Mis à part la commodité syntaxique, la combinaison de types singleton, de types dépendants du chemin et de valeurs implicites signifie que Scala a un support étonnamment bon pour le typage dépendant, comme j'ai essayé de le démontrer dans informe .
Le support intrinsèque de Scala pour les types dépendants se fait via les types dépendant du chemin . Celles-ci permettent à un type de dépendre d'un chemin de sélecteur à travers un graphique objet (c'est-à-dire valeur) comme ceci,
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 à la question "Scala est-il un langage typé de manière dépendante?" dans le positif: 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 "entièrement" dépendant car il n'a pas de somme et de types de produit dépendants comme ceux trouvés dans Agda ou Coq ou Idris comme intrinsèques. Je pense que cela reflète dans une certaine mesure une fixation sur la forme sur les fondamentaux, néanmoins, je vais essayer de montrer que Scala est beaucoup plus proche de ces autres langues que ce qui est généralement admis.
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 valeur. Ceci est directement représentable dans 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, c'est une partie cruciale de l' encodage des types de méthode dépendants qui est nécessaire pour échapper à la 'Bakery of Doom' dans Scala avant la version 2.10 (ou plus tôt via l'option du compilateur Scala expérimentale -Ydependent-method types).
Les types de produits dépendants (aka types Pi) sont essentiellement des fonctions allant des valeurs aux types. Ils sont essentiels à la représentation des vecteurs de taille statique et des autres enfants de l'affiche pour les langages de programmation typés de manière dépendante. Nous pouvons encoder des types Pi dans Scala en utilisant une combinaison de types dépendants du chemin, de types singleton et de paramètres implicites. On définit d'abord un trait qui va représenter une fonction d'une valeur de type T à un type U,
scala> trait Pi[T] { type U }
defined trait Pi
On peut que 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 pi.U
dans le type de résultat List[pi.U]
). Étant donné une valeur de type T, cette fonction renverra une liste (n vide) de valeurs du type correspondant à cette valeur T particulière.
Définissons maintenant quelques valeurs appropriées et témoins implicites pour les relations fonctionnelles que nous voulons entretenir,
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 utilisant le 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 que nous utilisons ici l' <:<
opérateur témoin de sous-type de Scala plutôt que =:=
parce que res2.type
et res3.type
sont des types singleton et donc plus précis que les types que nous vérifions sur le RHS).
En pratique, cependant, dans Scala, nous ne commencerions pas par encoder les types Sigma et Pi pour ensuite procéder à partir de là comme nous le ferions dans Agda ou Idris. Au lieu de cela, nous utiliserions des types dépendant du chemin, des types singleton et des implicits directement. Vous pouvez trouver de nombreux exemples de la façon dont cela se joue dans l'informe: types de taille , enregistrements extensibles , HLists complètes , abandonnez votre passe-partout , Zippers génériques , etc. etc.
La seule objection restante que je peux voir est que dans le codage ci-dessus des types Pi, nous avons besoin que les types singleton des valeurs dépendantes soient exprimables. Malheureusement, dans Scala, cela n'est possible que pour les valeurs de types référence et non pour les valeurs de types non-référence (en particulier par exemple Int). C'est une honte, mais pas une difficulté intrinsèque: le vérificateur de type Scala représente les types singleton de valeurs non-référence en interne, et il y a eu quelques des expériences en les rendant directement exprimable. En pratique, nous pouvons contourner le problème avec un codage au niveau du type assez standard 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é de manière dépendante. Si c'est le cas, alors la même chose pourrait être dite pour le ML dépendant (qui n'autorise que les dépendances sur les valeurs des nombres naturels), ce qui serait une conclusion bizarre.