Bỏ qua sự tiện lợi về mặt cú pháp, sự kết hợp của các kiểu singleton, kiểu phụ thuộc vào đường dẫn và các giá trị ngầm định có nghĩa là Scala hỗ trợ tốt một cách đáng ngạc nhiên cho việc nhập phụ thuộc, như tôi đã cố gắng chứng minh trong shapeless .
Hỗ trợ nội tại của Scala cho các kiểu phụ thuộc là thông qua các kiểu phụ thuộc vào đường dẫn . Những điều này cho phép một kiểu phụ thuộc vào đường dẫn của bộ chọn thông qua một đồ thị đối tượng- (tức là. Value-) như vậy,
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]
Theo quan điểm của tôi, những điều trên đã đủ để trả lời câu hỏi "Scala có phải là một ngôn ngữ được gõ phụ thuộc không?" tích cực: rõ ràng là ở đây chúng ta có các kiểu được phân biệt bởi các giá trị là tiền tố của chúng.
Tuy nhiên, người ta thường phản đối rằng Scala không phải là một ngôn ngữ loại phụ thuộc "hoàn toàn" bởi vì nó không có tổng phụ thuộc và loại sản phẩm như được tìm thấy trong Agda hoặc Coq hoặc Idris như là bản chất. Tôi nghĩ rằng điều này phản ánh sự cố định về hình thức so với các nguyên tắc cơ bản ở một mức độ nào đó, tuy nhiên, tôi sẽ cố gắng và cho thấy rằng Scala gần với những ngôn ngữ khác hơn rất nhiều so với những gì thường được thừa nhận.
Mặc dù theo thuật ngữ, các kiểu tổng phụ thuộc (còn được gọi là kiểu Sigma) chỉ đơn giản là một cặp giá trị trong đó kiểu của giá trị thứ hai phụ thuộc vào giá trị đầu tiên. Điều này có thể đại diện trực tiếp trong 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
và trên thực tế, đây là một phần quan trọng của mã hóa các loại phương thức phụ thuộc cần thiết để thoát khỏi 'Bakery of Doom' trong Scala trước 2.10 (hoặc sớm hơn thông qua tùy chọn trình biên dịch Scala thử nghiệm -Ydependent-method type).
Các loại sản phẩm phụ thuộc (hay còn gọi là các loại Pi) về cơ bản là các hàm từ giá trị đến loại. Chúng là chìa khóa để biểu diễn các vectơ có kích thước tĩnh và các con áp phích khác cho các ngôn ngữ lập trình được định kiểu phụ thuộc. Chúng ta có thể mã hóa các kiểu Pi trong Scala bằng cách sử dụng kết hợp các kiểu phụ thuộc đường dẫn, kiểu singleton và các tham số ngầm định. Đầu tiên, chúng tôi xác định một đặc điểm sẽ biểu diễn một hàm từ giá trị kiểu T sang kiểu U,
scala> trait Pi[T] { type U }
defined trait Pi
Chúng ta không thể xác định một phương thức đa hình sử dụng kiểu này,
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]
(lưu ý việc sử dụng kiểu phụ thuộc đường dẫn pi.U
trong kiểu kết quả List[pi.U]
). Cho một giá trị kiểu T, hàm này sẽ trả về một danh sách (n trống) các giá trị của kiểu tương ứng với giá trị T cụ thể đó.
Bây giờ chúng ta hãy xác định một số giá trị phù hợp và các nhân chứng ngầm cho các mối quan hệ chức năng mà chúng ta muốn nắm giữ,
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
Và bây giờ đây là chức năng sử dụng kiểu Pi của chúng tôi đang hoạt động,
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]]
(lưu ý rằng ở đây chúng tôi sử dụng <:<
toán tử chứng kiến kiểu phụ của Scala thay =:=
vì bởi vì res2.type
và res3.type
là các kiểu singleton và do đó chính xác hơn các kiểu mà chúng tôi đang xác minh trên RHS).
Tuy nhiên, trong thực tế, trong Scala, chúng tôi sẽ không bắt đầu bằng cách mã hóa các loại Sigma và Pi và sau đó tiếp tục từ đó như chúng tôi làm trong Agda hoặc Idris. Thay vào đó, chúng tôi sẽ sử dụng các kiểu phụ thuộc đường dẫn, kiểu singleton và hàm ý trực tiếp. Bạn có thể tìm thấy rất nhiều ví dụ về cách điều này diễn ra trong không định hình: các loại có kích thước , bản ghi có thể mở rộng , danh sách HL toàn diện , loại bỏ bảng mẫu của bạn , Dây kéo chung , v.v.
Sự phản đối duy nhất còn lại mà tôi có thể thấy là trong mã hóa kiểu Pi ở trên, chúng ta yêu cầu các kiểu singleton của các giá trị phụ thuộc phải có thể biểu đạt được. Thật không may trong Scala, điều này chỉ có thể thực hiện được đối với các giá trị của kiểu tham chiếu chứ không phải đối với các giá trị của kiểu không tham chiếu (ví dụ như int). Đây là một sự xấu hổ, nhưng không phải là một khó khăn nội tại: kiểm tra loại Scala của đại diện các kiểu singleton các giá trị phi tài liệu tham khảo nội bộ, và đã có một vài các thí nghiệm trong việc đưa ra chúng trực tiếp có thể biểu. Trong thực tế, chúng ta có thể giải quyết vấn đề này với một bảng mã cấp kiểu khá chuẩn của các số tự nhiên .
Trong mọi trường hợp, tôi không nghĩ rằng giới hạn tên miền nhỏ này có thể được sử dụng như một sự phản đối trạng thái của Scala như một ngôn ngữ được đánh máy phụ thuộc. Nếu đúng như vậy, thì điều tương tự cũng có thể được nói cho Dependent ML (chỉ cho phép phụ thuộc vào các giá trị số tự nhiên), đây sẽ là một kết luận kỳ lạ.