En dehors de la commodité syntaxique, la combinaison des types singletons, des types dépendants du chemin et des valeurs implicites signifie que Scala a un support étonnamment bon pour le typage dépendant, comme j'ai essayé de le démontrer dans le document informe .
Le support intrinsèque de Scala pour les types dépendants se fait par le biais de types dépendants du chemin . Ils permettent à un type de dépendre d'un chemin de sélection à travers un graphe d'objets (c'est-à-dire de valeurs) comme suit,
scala> class Foo { class Bar }
defined class Foo
scala> val foo1 = new Foo
foo1: Foo = Foo@24bc0658
scala> val foo2 = new Foo
foo2: Foo = Foo@6f7f757
scala> implicitly[foo1.Bar =:= foo1.Bar] // OK: equal types
res0: =:=[foo1.Bar,foo1.Bar] = <function1>
scala> implicitly[foo1.Bar =:= foo2.Bar] // Not OK: unequal types
<console>:11: error: Cannot prove that foo1.Bar =:= foo2.Bar.
implicitly[foo1.Bar =:= foo2.Bar]
À mon avis, ce qui précède devrait suffire à répondre positivement à la question "Scala est-il un langage typé de manière dépendante ?": il est clair que nous avons ici des types qui se distinguent par les valeurs qui sont leurs préfixes.
Cependant, on objecte souvent que Scala n'est pas un langage de type dépendant "complet" parce qu'il ne dispose pas de somme dépendante et types de produits comme on en trouve dans Agda ou Coq ou Idris comme intrinsèques. Je pense que cela reflète dans une certaine mesure une fixation sur la forme plutôt que sur les fondamentaux, néanmoins, je vais essayer de montrer que Scala est beaucoup plus proche de ces autres langages que ce qui est généralement reconnu.
Malgré la terminologie, les types de somme dépendants (également appelés types Sigma) sont simplement une paire de valeurs où le type de la deuxième valeur dépend de la première. Ceci est directement représentable en Scala,
scala> trait Sigma {
| val foo: Foo
| val bar: foo.Bar
| }
defined trait Sigma
scala> val sigma = new Sigma {
| val foo = foo1
| val bar = new foo.Bar
| }
sigma: java.lang.Object with Sigma{val bar: this.foo.Bar} = $anon$1@e3fabd8
et en fait, il s'agit d'une partie cruciale de la l'encodage des types de méthodes dépendantes qui est nécessaire pour échapper à la "boulangerie du destin". dans Scala avant la version 2.10 (ou avant via l'option de compilation Scala expérimentale -Ydependent-method types).
Les types de produits dépendants (alias types Pi) sont essentiellement des fonctions de valeurs à types. Ils sont essentiels à la représentation des vecteurs statiquement dimensionnés et les autres enfants-vedettes des langages de programmation typés de manière dépendante. Nous pouvons encoder les types Pi en Scala en utilisant une combinaison de types dépendants du chemin, de types singleton et de paramètres implicites. Tout d'abord, nous définissons un trait qui va représenter une fonction d'une valeur de type T vers un type U,
scala> trait Pi[T] { type U }
defined trait Pi
Nous pouvons ensuite définir une méthode polymorphe qui utilise ce type,
scala> def depList[T](t: T)(implicit pi: Pi[T]): List[pi.U] = Nil
depList: [T](t: T)(implicit pi: Pi[T])List[pi.U]
(notez l'utilisation du type dépendant du chemin d'accès pi.U
dans le type de résultat List[pi.U]
). Étant donné une valeur de type T, cette fonction renvoie une liste (n vide) de valeurs de type correspondant à cette valeur T particulière.
Définissons maintenant des valeurs appropriées et des témoins implicites pour les relations fonctionnelles que nous voulons maintenir,
scala> object Foo
defined module Foo
scala> object Bar
defined module Bar
scala> implicit val fooInt = new Pi[Foo.type] { type U = Int }
fooInt: java.lang.Object with Pi[Foo.type]{type U = Int} = $anon$1@60681a11
scala> implicit val barString = new Pi[Bar.type] { type U = String }
barString: java.lang.Object with Pi[Bar.type]{type U = String} = $anon$1@187602ae
Et maintenant, voici notre fonction d'utilisation du type Pi- en action,
scala> depList(Foo)
res2: List[fooInt.U] = List()
scala> depList(Bar)
res3: List[barString.U] = List()
scala> implicitly[res2.type <:< List[Int]]
res4: <:<[res2.type,List[Int]] = <function1>
scala> implicitly[res2.type <:< List[String]]
<console>:19: error: Cannot prove that res2.type <:< List[String].
implicitly[res2.type <:< List[String]]
^
scala> implicitly[res3.type <:< List[String]]
res6: <:<[res3.type,List[String]] = <function1>
scala> implicitly[res3.type <:< List[Int]]
<console>:19: error: Cannot prove that res3.type <:< List[Int].
implicitly[res3.type <:< List[Int]]
(notez qu'ici, nous utilisons l'option <:<
opérateur de sous-type-témoin plutôt que =:=
parce que res2.type
et res3.type
sont des types singuliers et donc plus précis que les types que nous vérifions sur la droite).
En pratique, cependant, en Scala, nous ne commencerions pas par coder les types Sigma et Pi pour ensuite procéder à partir de là comme nous le ferions en Agda ou Idris. Nous utiliserions plutôt directement les types dépendants du chemin, les types singleton et les implicites. Vous pouvez trouver de nombreux exemples de la façon dont cela se passe dans shapeless : types dimensionnés , enregistrements extensibles , listes complètes de HL , supprimez votre modèle standard , Fermetures à glissière génériques etc. etc.
La seule objection restante que je vois est que dans l'encodage des types Pi ci-dessus, nous avons besoin que les types singleton des valeurs dépendantes soient exprimables. Malheureusement, en Scala, cela n'est possible que pour les valeurs de types de référence et non pour les valeurs de types non-référence (par exemple, Int). C'est dommage, mais ce n'est pas une difficulté intrinsèque : Le vérificateur de type de Scala représente les types singletons des valeurs de non-référence en interne, et il y a eu un certain nombre de cas où le vérificateur de type de Scala n'a pas été en mesure d'exprimer les valeurs de référence. couple de expériences en les rendant directement exprimables. Dans la pratique, nous pouvons contourner le problème avec un encodage assez standard au niveau du type des nombres naturels .
Dans tous les cas, je ne pense pas que cette légère restriction de domaine puisse être utilisée comme une objection au statut de Scala en tant que langage typé dépendant. Si c'est le cas, alors on pourrait dire la même chose de Dependent ML (qui n'autorise les dépendances que sur les valeurs des nombres naturels), ce qui serait une conclusion bizarre.
3 votes
Les concepteurs de Scala estiment que le cube lambda de Barendregt n'est pas la panacée de la théorie des types. Cela pourrait ou non être la raison.
8 votes
@JörgWMittag Qu'est-ce que le Lamda Cube ? Une sorte d'appareil magique ?
0 votes
@ashy_32bit voir l'article de Barendregt "Introduction to Generalised Type Systems" ici : diku.dk/hjemmesider/ansatte/henglein/papers/barendregt1991.pdf