Зависимые типы методов, которые раньше были экспериментальной функцией, теперь включены по умолчанию в магистрали , и, по-видимому, это, похоже, вызвало некоторое волнение в сообществе Scala.
На первый взгляд не сразу понятно, для чего это может быть полезно. Хайко Seeberger опубликовал простой пример зависимых типов методов здесь , которые , как можно видеть , в комментарии может быть легко воспроизведен с параметрами типа по методам. Так что это был не очень убедительный пример. (Возможно, мне не хватает чего-то очевидного. Поправьте меня, если это так.)
Каковы практические и полезные примеры использования зависимых типов методов, в которых они явно превосходят альтернативы?
Что интересного мы можем сделать с ними, что раньше было невозможно / легко?
Что они покупают нам по сравнению с существующими функциями системы типов?
Кроме того, являются ли типы зависимых методов аналогичными или черпают вдохновение в каких-либо функциях систем типов других продвинутых типизированных языков, таких как Haskell, OCaml?
источник
Ответы:
Более или менее любое использование членов (т. Е. Вложенных) типов может вызвать потребность в зависимых типах методов. В частности, я утверждаю, что без зависимых типов методов классический паттерн торта больше похож на антишаблон.
Так в чем проблема? Вложенные типы в Scala зависят от их включающего экземпляра. Следовательно, при отсутствии зависимых типов методов попытки использовать их вне этого экземпляра могут быть удручающе трудными. Это может превратить дизайн, который поначалу казался элегантным и привлекательным, в чудовищно жесткие и трудно поддающиеся рефакторингу.
Я проиллюстрирую это упражнением, которое я даю во время моего учебного курса Advanced Scala ,
Это пример классического паттерна торта: у нас есть семейство абстракций, которые постепенно уточняются посредством иерархии (
ResourceManager
/Resource
уточняются с помощьюFileManager
/,File
которые, в свою очередь, уточняются с помощьюNetworkFileManager
/RemoteFile
). Это игрушечный пример, но шаблон реальный: он используется во всем компиляторе Scala и широко использовался в плагине Scala Eclipse.Вот пример используемой абстракции,
Обратите внимание, что зависимость пути означает, что компилятор гарантирует, что
testHash
testDuplicates
методы иNetworkFileManager
можно вызывать только с аргументами, которые им соответствуют, т.е. это собственноеRemoteFiles
, и ничего больше.Это, несомненно, желательное свойство, но предположим, мы хотим переместить этот тестовый код в другой исходный файл? С зависимыми типами методов тривиально легко переопределить эти методы вне
ResourceManager
иерархии,Обратите внимание на использование типов зависимых методов здесь: тип второго аргумента (
rm.Resource
) зависит от значения первого аргумента (rm
).Это можно сделать без зависимых типов методов, но это крайне неудобно, а механизм совершенно не интуитивно понятен: я преподаю этот курс уже почти два года, и за это время никто не придумал рабочего решения без подсказки.
Попробуйте сами ...
После непродолжительной борьбы с этим вы, вероятно, поймете, почему я (или, может быть, это был Дэвид Макивер, мы не можем вспомнить, кто из нас придумал этот термин) называю это Пекарней Судьбы.
Изменить: консенсус заключается в том, что Bakery of Doom была монетой Дэвида Макивера ...
В качестве бонуса: форма зависимых типов в Scala в целом (и зависимые типы методов как ее часть) была вдохновлена языком программирования Beta ... они естественным образом возникают из последовательной семантики вложенности Beta. Я не знаю ни одного другого, хотя бы отчасти основного языка программирования, который имел бы зависимые типы в этой форме. Такие языки, как Coq, Cayenne, Epigram и Agda, имеют другую форму зависимой типизации, которая в некотором роде является более общей, но значительно отличается тем, что является частью систем типов, которые, в отличие от Scala, не имеют подтипов.
источник
def testHash4[R <: ResourceManager#BasicResource](rm: ResourceManager { type Resource = R }, r: R) = assert(r.hash == "9e47088d")
Я полагаю, что это можно считать еще одной формой зависимых типов.Где-то еще мы можем статически гарантировать, что мы не смешиваем узлы из двух разных графов, например:
Конечно, это уже работало, если оно определено внутри
Graph
, но скажем, что мы не можем модифицироватьGraph
и пишем для него расширение «pimp my library».По поводу второго вопроса: типы, поддерживаемые этой функцией, намного слабее, чем полные зависимые типы (см. « Зависимо типизированное программирование» в Agda, чтобы понять это.) Я не думаю, что видел аналогию раньше.
источник
Эта новая функция необходима, когда вместо параметров типа используются конкретные элементы абстрактного типа . Когда используются параметры типа, зависимость типа полиморфизма семейства может быть выражена в последней и некоторых более старых версиях Scala, как в следующем упрощенном примере.
источник
trait C {type A}; def f[M](a: C { type A = M}, b: M) = 0;class CI extends C{type A=Int};class CS extends C{type A=String}
и т. Д.Я разрабатываю модель взаимодействия формы декларативного программирования с состоянием среды. Детали здесь не актуальны (например, подробности об обратных вызовах и концептуальном сходстве с моделью Актера в сочетании с Сериализатором).
Актуальная проблема заключается в том, что значения состояний хранятся в хэш-карте и на них ссылается значение хеш-ключа. Функции вводят неизменяемые аргументы, которые являются значениями из среды, могут вызывать другие такие функции и записывать состояние в среду. Но функциям не разрешено читать значения из среды (поэтому внутренний код функции не зависит от порядка изменения состояния и, таким образом, остается декларативным в этом смысле). Как это набрать в Scala?
Класс среды должен иметь перегруженный метод, который вводит такую функцию для вызова и вводит хеш-ключи аргументов функции. Таким образом, этот метод может вызывать функцию с необходимыми значениями из хэш-карты, не предоставляя публичный доступ для чтения к значениям (таким образом, по мере необходимости, лишая функциям возможность читать значения из среды).
Но если эти хэш-ключи являются строками или целочисленными значениями хеш-функции, статическая типизация типа элемента хэш-карты относится к Any или AnyRef (код хэш-карты не показан ниже), и, таким образом, может возникнуть несоответствие во время выполнения, т.е. для помещения любого типа значения в хеш-карту для данного хеш-ключа.
Хотя я не тестировал следующее, теоретически я могу получить хэш-ключи из имен классов во время выполнения
classOf
, поэтому хеш-ключ - это имя класса, а не строка (с использованием обратных ссылок Scala для вставки строки в имя класса).Таким образом достигается безопасность статического типа.
источник
def callit[A](argkeys: Tuple[DependentHashKey,DependentHashKey])(func: Env => argkeys._0.ValueType => argkeys._1.ValueType => A): A
. Мы не будем использовать набор ключей аргументов, потому что типы элементов будут включены (неизвестны во время компиляции) в тип коллекции.