Существуют типы, зависящие от пути, и я думаю, что в Scala можно выразить почти все функции таких языков, как Epigram или Agda, но мне интересно, почему Scala не поддерживает это более явно, как это очень хорошо в других областях (скажем, , DSL)? Что-нибудь, что мне не хватает, например, «это не обязательно»?
scala
path-dependent-type
dependent-type
shapeless
Ашкан Х. Назарий
источник
источник
Ответы:
Помимо синтаксического удобства, сочетание одноэлементных типов, типов, зависящих от пути и неявных значений, означает, что Scala имеет на удивление хорошую поддержку зависимой типизации, как я пытался продемонстрировать в shapeless .
Встроенная поддержка зависимых типов в Scala осуществляется через типы, зависящие от пути . Они позволяют типу зависеть от пути к селектору через граф объекта (т. Е. Значения) следующим образом:
На мой взгляд, приведенного выше должно быть достаточно, чтобы ответить на вопрос «Является ли Scala языком с зависимой типизацией?» в положительном свете: ясно, что здесь есть типы, которые различаются значениями, являющимися их префиксами.
Однако часто возражают, что Scala не является «полностью» языком зависимых типов, потому что он не имеет зависимых сумм и типов продуктов, как в Agda, Coq или Idris, в качестве встроенных функций. Я думаю, что это в некоторой степени отражает зацикленность на форме над основами, тем не менее, я постараюсь показать, что Scala намного ближе к этим другим языкам, чем это обычно считается.
Несмотря на терминологию, типы зависимых сумм (также известные как типы сигма) - это просто пара значений, где тип второго значения зависит от первого значения. Это можно напрямую представить в Scala,
и на самом деле это важная часть кодирования типов зависимых методов, которая необходима для выхода из «Bakery of Doom» в Scala до 2.10 (или ранее с помощью экспериментальной опции компилятора Scala -Ydependent-method types).
Зависимые типы продуктов (также известные как Pi-типы) по сути являются функциями от значений к типам. Они являются ключом к представлению векторов статического размера и других дочерних элементов для зависимо типизированных языков программирования. Мы можем кодировать типы Pi в Scala, используя комбинацию типов, зависящих от пути, одноэлементных типов и неявных параметров. Сначала мы определяем типаж, который будет представлять функцию от значения типа T до типа U,
Затем мы можем определить полиморфный метод, который использует этот тип,
(обратите внимание на использование зависимого от пути типа
pi.U
в типе результатаList[pi.U]
). Учитывая значение типа T, эта функция вернет (n пустой) список значений типа, соответствующего этому конкретному значению T.Теперь давайте определим некоторые подходящие значения и неявные свидетели функциональных отношений, которые мы хотим поддерживать,
А теперь вот наша функция использования Pi-типа в действии,
(обратите внимание, что здесь мы используем
<:<
оператор Scala -свидетеля подтипа, а не=:=
потому, чтоres2.type
иres3.type
являются одноэлементными типами и, следовательно, более точными, чем типы, которые мы проверяем на RHS).Однако на практике в Scala мы не начинаем с кодирования типов Sigma и Pi, а затем продолжаем оттуда, как в Agda или Idris. Вместо этого мы будем напрямую использовать типы, зависящие от пути, одиночные типы и имплициты. Вы можете найти множество примеров того, как это проявляется в бесформенных: типы с размерами , расширяемые записи , исчерпывающие списки HList , отбросьте свой шаблон , общие застежки-молнии и т. Д. И т. Д.
Единственное оставшееся возражение, которое я вижу, заключается в том, что в приведенном выше кодировании типов Pi мы требуем, чтобы одноэлементные типы зависимых значений были выражены. К сожалению, в Scala это возможно только для значений ссылочных типов, но не для значений не ссылочных типов (особенно, например, Int). Это позор, но не внутренняя трудность: тип проверки Scala представляет типы одноэлементные из не-эталонных значений внутри, и там было несколько из экспериментов сделать их непосредственно представимы. На практике мы можем обойти проблему с помощью довольно стандартного кодирования натуральных чисел на уровне типов .
В любом случае, я не думаю, что это небольшое ограничение домена можно использовать в качестве возражения против статуса Scala как языка с зависимой типизацией. Если это так, то то же самое можно сказать и о зависимом ML (который допускает зависимости только от значений натуральных чисел), что было бы странным выводом.
источник
Я предполагаю, что это потому, что (как я знаю по опыту, используя зависимые типы в помощнике доказательства Coq, который полностью поддерживает их, но все же не очень удобным способом) зависимые типы - это очень продвинутая функция языка программирования, которую действительно сложно правильно - и на практике это может вызвать экспоненциальный взрыв сложности. Они по-прежнему являются предметом исследований в области информатики.
источник
Я считаю, что зависящие от пути типы в Scala могут представлять только Σ-типы, но не Π-типы. Это:
не совсем Π-тип. По определению, Π-тип или зависимый продукт - это функция, тип результата которой зависит от значения аргумента, представляющего универсальный квантор, то есть ∀x: A, B (x). Однако в приведенном выше случае это зависит только от типа T, но не от некоторого значения этого типа. Сама характеристика Pi является Σ-типом, квантором существования, то есть ∃x: A, B (x). Само-ссылка объекта в этом случае действует как количественная переменная. Однако при передаче в качестве неявного параметра он сводится к функции обычного типа, поскольку разрешается по типам. Кодировка зависимого продукта в Scala может выглядеть следующим образом:
Недостаток здесь - возможность статически ограничить поле x ожидаемым значением t, эффективно формируя уравнение, представляющее свойство всех значений, принадлежащих типу T. Вместе с нашими Σ-типами, используемыми для выражения существования объекта с данным свойством, формируется логика, в которой наше уравнение является теоремой, требующей доказательства.
Кстати, в реальном случае теорема может быть весьма нетривиальной, вплоть до того момента, когда ее нельзя будет автоматически вывести из кода или решить без значительных усилий. Можно даже сформулировать гипотезу Римана таким образом, только чтобы обнаружить, что сигнатуру невозможно реализовать без ее фактического доказательства, бесконечного цикла или выдачи исключения.
источник
Pi
для создания типов в зависимости от значений.depList
извлекает типU
изPi[T]
, выбранного для типа (не значения)t
. Этот тип является одноэлементным типом, который в настоящее время доступен для одноэлементных объектов Scala и представляет их точные значения. В примере создается одна реализация дляPi
каждого типа объекта-синглтона, таким образом объединяя тип со значением, как в Σ-типе. Π-тип, с другой стороны, представляет собой формулу, которая соответствует структуре входного параметра. Возможно, в Scala их нет, потому что Π-типы требуют, чтобы каждый тип параметра был GADT, а Scala не отличает GADT от других типов.pi.U
в примере Майлза не считается зависимым типом? Все дело в стоимостиpi
.pi.U
зависит от значенияpi
. Проблема, препятствующаяtrait Pi[T]
превращению в Π-тип, заключается в том, что мы не можем сделать его зависимым от значения произвольного аргумента (например,t
indepList
) без отмены этого аргумента на уровне типа.Вопрос касался более прямого использования функции зависимой типизации, и, на мой взгляд, было бы полезно иметь более прямой подход зависимой типизации, чем то, что предлагает Scala.
Текущие ответы пытаются аргументировать вопрос на теоретико-типовом уровне. Я хочу придать этому более прагматичный вид. Это может объяснить, почему люди разделены по уровню поддержки зависимых типов в языке Scala. Мы можем иметь в виду несколько иные определения. (не сказать, что один прав, а другой нет).
Это не попытка ответить на вопрос, насколько легко было бы превратить Scala во что-то вроде Идриса (я думаю, очень сложно) или написать библиотеку, предлагающую более прямую поддержку возможностей, подобных Идрису (например,
singletons
пытается быть в Haskell).Вместо этого я хочу подчеркнуть прагматическую разницу между Scala и таким языком, как Idris.
Что такое биты кода для выражений на уровне значений и типов? Идрис использует тот же код, Scala использует совсем другой код.
Scala (аналогично Haskell) может кодировать множество вычислений на уровне типов. Это показывают библиотеки вроде
shapeless
. Эти библиотеки делают это с помощью действительно впечатляющих и умных приемов. Однако их код уровня типа (в настоящее время) сильно отличается от выражений уровня значений (я считаю, что этот пробел в Haskell несколько ближе). Идрис позволяет использовать выражение уровня значения на уровне типа КАК ЕСТЬ.Очевидным преимуществом является повторное использование кода (вам не нужно кодировать выражения уровня типа отдельно от уровня значения, если они нужны в обоих местах). Должно быть намного проще писать код ценностного уровня. Должно быть проще не иметь дело с хаками, такими как синглтоны (не говоря уже о стоимости производительности). Вам не нужно изучать две вещи, вы узнаете одну вещь. На прагматическом уровне нам нужно меньше концепций. Синонимы типов, семейства типов, функции ... как насчет только функций? На мой взгляд, эти объединяющие преимущества гораздо глубже и заключаются не только в удобстве синтаксиса.
Считайте проверенный код. См.
Https://github.com/idris-lang/Idris-dev/blob/v1.3.0/libs/contrib/Interfaces/Verified.idr
типов проверяет доказательства монадических / функторных / аппликативных законов, и доказательства касаются фактических реализации монады / функтора / аппликатива, а не какой-либо эквивалент уровня закодированного типа, который может быть одинаковым или не одинаковым. Большой вопрос в том, что мы доказываем?
То же самое можно сделать, используя хитрые приемы кодирования (см. Следующую версию для Haskell, я не видел ее для Scala)
https://blog.jle.im/entry/verified-instances-in-haskell.html
https: // github.com/rpeszek/IdrisTddNotes/wiki/Play_FunctorLaws,
за исключением того, что типы настолько сложны, что трудно увидеть законы, выражения уровня значения преобразуются (автоматически, но все же) в элементы уровня типа, и вы также должны доверять этому преобразованию . Во всем этом есть место для ошибки, которая как бы противоречит цели использования компилятора в качестве помощника по проверке.
(Отредактировано 2018.8.10) Говоря о помощи с доказательствами, вот еще одна большая разница между Idris и Scala. В Scala (или Haskell) нет ничего, что могло бы помешать написанию расходящихся доказательств:
в то время как у Идриса есть
total
ключевое слово, предотвращающее компиляцию такого кода.Библиотека Scala, которая пытается объединить код уровня значения и типа (например, Haskell
singletons
), могла бы стать интересным тестом для поддержки Scala зависимых типов. Можно ли сделать такую библиотеку намного лучше в Scala из-за типов, зависящих от пути?Я слишком новичок в Scala, чтобы сам отвечать на этот вопрос.
источник