Синтаксична зручність убік, поєднання однотонних типів, залежних від шляху типів та неявних значень означає, що Scala на диво добре підтримує залежне введення тексту, як я намагався продемонструвати безформним .
Власна підтримка Scala для залежних типів здійснюється через типи, що залежать від шляху . Вони дозволяють типу залежати від шляху вибору через графік об'єкта (тобто значення-),
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]
На мою думку, сказаного повинно бути достатньо, щоб відповісти на питання "Чи є Scala залежно набраною мовою?" позитивно: зрозуміло, що тут ми маємо типи, які відрізняються значеннями, які є їх префіксами.
Однак часто заперечують, що Scala не є "повністю" мовою залежного типу, оскільки вона не має залежно від суми та типів продуктів, як це можна знайти в Agda, Coq або Idris як властивих. Я думаю, що це певною мірою відображає фіксацію форми над фундаментальними, однак я спробую показати, що Скала набагато ближче до цих інших мов, ніж зазвичай визнається.
Незважаючи на термінологію, типи залежних сум (також відомі як типи Sigma) - це просто пара значень, де тип другого значення залежить від першого значення. Це безпосередньо представлено в 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
насправді, це важлива частина кодування залежних типів методу, яка необхідна для виходу з "Пекарні Дум" у Скалі до 2.10 (або раніше за допомогою експериментального варіанту компілятора типів "Скала").
Залежні типи виробів (ака Pi-типи) по суті є функціями від значень до типів. Вони є ключовими для представлення векторів статичного розміру та інших дітей, що плакують, для залежно введених мов програмування. Ми можемо кодувати типи Pi в Scala, використовуючи комбінацію типів, залежних від шляху, однотонних типів та неявних параметрів. Спочатку ми визначаємо ознаку, яка буде представляти функцію від значення типу T до типу U,
scala> trait Pi[T] { type U }
defined trait Pi
Ми можемо, ніж визначити поліморфний метод, який використовує цей тип,
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
результату List[pi.U]
). Враховуючи значення типу T, ця функція поверне (n порожній) список значень типу, що відповідає цьому конкретному значенню T.
Тепер давайте визначимо деякі відповідні значення та неявні свідки для функціональних відносин, які ми хочемо мати,
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
А тепер наша функція використання типу Pi в дії,
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]]
(Зверніть увагу , що тут ми використовуємо в Scala <:<
підтип-свідка оператор , а не =:=
тому , що res2.type
і res3.type
є одноелементними типами і , отже , більш точними , ніж типів ми перевірочні на РІТ).
На практиці, однак, у Scala ми б не починали з кодування типів Sigma та Pi, а потім переходили звідти, як у Agda чи Idris. Натомість ми б використовували безпосередньо залежні від шляху типи, однотонні типи та імпліцити безпосередньо. Ви можете знайти численні приклади того, як це відбувається у безформних формах : типи розмірів , розширювані записи , вичерпні списки HL , брухт вашої котлової панелі , загальні блискавки тощо.
Єдине, що я бачу, заперечення - це те, що у вищезазначеному кодуванні типів Pi потрібно, щоб однотипні типи залежних від них значень були вираженими. На жаль, у Scala це можливо лише для значень еталонних типів, а не для значень нереференційних типів (наприклад, Int.). Це ганьба, але не внутрішня труднощі: тип перевірки Scala являє типи одноелементні з не-еталонних значень всередині, і там було кілька з експериментів зробити їх безпосередньо представимо. На практиці ми можемо подолати проблему за допомогою досить стандартного кодування на рівні типу натуральних чисел .
У будь-якому випадку, я не думаю, що це незначне обмеження домену може використовуватися як заперечення щодо статусу Scala як мови, що набирається залежно. Якщо це так, то те ж саме можна сказати і про залежний ML (який допускає лише залежності від натуральних значень чисел), що було б химерним висновком.