Sözdizimsel kolaylık bir yana, tekli türlerin, yola bağlı türlerin ve örtük değerlerin kombinasyonu, şekilsiz olarak göstermeye çalıştığım gibi, Scala'nın bağımlı yazım için şaşırtıcı derecede iyi bir desteğe sahip olduğu anlamına gelir .
Scala'nın bağımlı türler için içsel desteği, yola bağlı türler aracılığıyladır . Bunlar, bir türün bir nesne- (yani değer-) grafiği boyunca bir seçici yoluna bağlı olmasına izin verir.
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]
Benim görüşüme göre, yukarıdakiler "Scala bağımlı yazılan bir dil mi?" Sorusuna yanıt vermek için yeterli olmalıdır. olumlu olarak: burada, ön ekleri olan değerlerle ayırt edilen türlerimiz olduğu açıktır.
Ancak, Agda veya Coq veya Idris'te içsel olarak bulunan bağımlı toplam ve ürün türlerine sahip olmadığı için Scala'nın "tamamen" bağımlı bir tür dili olmadığı sıklıkla itiraz edilir . Sanırım bu, bir dereceye kadar temeller üzerinde biçim üzerine bir saplantıyı yansıtıyor, yine de, Scala'nın bu diğer dillere tipik olarak kabul edilenden çok daha yakın olduğunu göstermeye çalışacağım.
Terminolojiye rağmen, bağımlı toplam türleri (Sigma türleri olarak da bilinir), yalnızca ikinci değerin türünün birinci değere bağlı olduğu bir değer çiftidir. Bu doğrudan Scala'da temsil edilebilir,
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
ve aslında bu, 2.10'dan önce (veya daha önce deneysel -Ydependent-yöntem türleri Scala derleyici seçeneği aracılığıyla) Scala'daki 'Bakery of Doom'dan kaçmak için gereken bağımlı yöntem türlerinin kodlanmasının çok önemli bir parçasıdır .
Bağımlı ürün türleri (diğer adıyla Pi türleri), temelde değerlerden türlere işlevlerdir. Statik olarak boyutlandırılmış vektörlerin ve bağımlı olarak yazılmış programlama dilleri için diğer poster çocuklarının temsilinin anahtarıdırlar . Scala'daki Pi türlerini yola bağlı türler, tekli türler ve örtük parametrelerin bir kombinasyonunu kullanarak kodlayabiliriz. İlk önce, T tipi bir değerden U tipine kadar bir işlevi temsil edecek bir özellik tanımlarız,
scala> trait Pi[T] { type U }
defined trait Pi
Daha sonra bu türü kullanan polimorfik bir yöntem tanımlayabiliriz,
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]
( pi.U
sonuç türünde yola bağımlı türün kullanımına dikkat edin List[pi.U]
). T türünde bir değer verildiğinde, bu işlev söz konusu T değerine karşılık gelen türden bir (n boş) değer listesi döndürür.
Şimdi, tutmak istediğimiz işlevsel ilişkiler için bazı uygun değerler ve örtük tanıklar tanımlayalım,
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
Ve şimdi burada Pi tipi kullanma işlevimiz iş başında,
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]]
(not burada Scala'nın kullandıkları <:<
yerine alt tip-tanık operatörü =:=
çünkü res2.type
ve res3.type
tekil türleri ve biz rhs doğruluyorsanız türlerinden daha dolayısıyla daha hassas olan).
Ancak pratikte Scala'da Sigma ve Pi türlerini kodlayarak başlayıp ardından Agda veya Idris'te yaptığımız gibi oradan devam etmezdik. Bunun yerine, yola bağlı türleri, tekli türleri ve dolaylı uygulamaları kullanırdık. Bunun şekilsiz olarak nasıl oynandığına dair çok sayıda örnek bulabilirsiniz: boyutlu tipler , genişletilebilir kayıtlar , kapsamlı HList'ler , kazan plakanızı hurdaya ayırın , jenerik Fermuarlar vb.
Görebildiğim tek itiraz, Pi türlerinin yukarıdaki kodlamasında, bağlı değerlerin tekil türlerinin ifade edilebilir olmasını istediğimizdir. Ne yazık ki Scala'da bu sadece referans tiplerinin değerleri için mümkündür ve referans olmayan tiplerin değerleri için mümkün değildir (özellikle örneğin Int). Scala'nın tip denetleyicisi dahili olmayan referans değerlerinin tekil türlerini temsil eder ve bir olmuştur: Bu bir utanç değil, içsel bir zorluktur çiftin ait deneyler onları doğrudan ifade edilebilen yapımında. Pratikte , doğal sayıların oldukça standart bir tür düzeyinde kodlamasıyla sorunu çözebiliriz .
Her durumda, bu küçük alan kısıtlamasının Scala'nın bağımlı olarak yazılmış bir dil statüsüne bir itiraz olarak kullanılabileceğini düşünmüyorum. Eğer öyleyse, aynı şey Bağımlı ML için de söylenebilir (bu sadece doğal sayı değerlerine bağımlılıklara izin verir) ve bu tuhaf bir sonuç olur.