Каковы некоторые убедительные примеры использования зависимых типов методов?
зависимые типы методов, которые раньше были экспериментальной функцией, теперь были включен по умолчанию в багажнике, и, видимо, это, кажется, создало какие-то волнения в сообществе Scala.
на первый взгляд, не сразу понятно, для чего это может быть полезно. Хейко Зеебергер опубликовал простой пример зависимых типов методов здесь, который, как видно из комментария, можно легко воспроизвести с помощью введите параметры для методов. Так что это был не очень убедительный пример. (Возможно, я упускаю что-то очевидное. Пожалуйста, поправьте меня, если это так.)
Каковы некоторые практические и полезные примеры вариантов использования для зависимых типов методов, где они явно выгодны по сравнению с альтернативами?
какие интересные вещи мы можем сделать с ними, что не возможно/легко раньше?
что они покупают нам по сравнению с существующими функциями системы типов?
кроме того, зависимые типы методов аналогичны или черпают вдохновение из любых функций, найденных в системах типов других продвинутых типизированных языков, таких как Haskell, OCaml?
4 ответа:
более или менее любое использование члена (т. е. вложенные) типы могут вызвать необходимость в зависимых типах методов. В частности, я утверждаю, что без зависимых типов методов классический шаблон торта ближе к анти-шаблону.
так в чем проблема? Вложенные типы в Scala зависят от их включающего экземпляра. Следовательно, в отсутствие зависимых типов методов попытки использовать их вне этого экземпляра могут быть удручающе трудными. Это может превратить конструкции которые изначально кажутся элегантными и привлекательными в чудовищах, которые кошмарно жесткие и трудно рефакторинг.
я проиллюстрирую это упражнением, которое я даю во время моего продвинутый учебный курс Scala,
trait ResourceManager { type Resource <: BasicResource trait BasicResource { def hash : String def duplicates(r : Resource) : Boolean } def create : Resource // Test methods: exercise is to move them outside ResourceManager def testHash(r : Resource) = assert(r.hash == "9e47088d") def testDuplicates(r : Resource) = assert(r.duplicates(r)) } trait FileManager extends ResourceManager { type Resource <: File trait File extends BasicResource { def local : Boolean } override def create : Resource } class NetworkFileManager extends FileManager { type Resource = RemoteFile class RemoteFile extends File { def local = false def hash = "9e47088d" def duplicates(r : Resource) = (local == r.local) && (hash == r.hash) } override def create : Resource = new RemoteFile }
это пример классической модели торта: у нас есть семейство абстракций, которые постепенно уточняются через наследственность (
ResourceManager
/Resource
уточненFileManager
/File
, которые, в свою очередь, уточненноеNetworkFileManager
/RemoteFile
). Это игрушечный пример, но шаблон реален: он используется во всем компиляторе Scala и широко использовался в плагине Scala Eclipse.вот пример используемой абстракции,
val nfm = new NetworkFileManager val rf : nfm.Resource = nfm.create nfm.testHash(rf) nfm.testDuplicates(rf)
обратите внимание, что зависимость пути означает, что компилятор гарантирует, что
testHash
иtestDuplicates
методыNetworkFileManager
можно назвать только с аргументами, которые ему соответствуют, т. е.. это свойRemoteFiles
, и больше ничего.это бесспорно a желательное свойство, но предположим, что мы хотим переместить этот тестовый код в другой исходный файл? С зависимыми типами методов тривиально легко переопределить эти методы вне
ResourceManager
иерархияdef testHash4(rm : ResourceManager)(r : rm.Resource) = assert(r.hash == "9e47088d") def testDuplicates4(rm : ResourceManager)(r : rm.Resource) = assert(r.duplicates(r))
обратите внимание на использование зависимых типов метод: тип второго аргумента (
rm.Resource
) зависит от значения первого аргумента (rm
).это можно сделать без зависимых типов методов, но это очень неудобно, и механизм довольно неинтуитивно: я преподаю этот курс уже почти два года, и за это время никто не придумал рабочее решение без соблазна.
попробуйте сами ...
// Reimplement the testHash and testDuplicates methods outside // the ResourceManager hierarchy without using dependent method types def testHash // TODO ... def testDuplicates // TODO ... testHash(rf) testDuplicates(rf)
после недолгой борьбы с этим вы, вероятно, узнаете, почему я (или, может быть, это был Дэвид Макивер, мы не можем вспомнить, кто из нас придумал этот термин) называю это пекарней судьбы.
Edit: консенсус в том, что пекарня судьбы была Дэвидом Макивером чеканка. ..
для бонуса: форма зависимых типов Scala в целом (и зависимые типы методов как ее часть) была вдохновлена языком программирования бета ... они возникают естественным образом из последовательной семантики вложенности бета. Я не знаю ни одного другого даже слегка основного языка программирования, который имеет зависимые типы в этой форме. Языки, такие как Coq, Cayenne, Epigram и Agda, имеют другую форму зависимой типизации, которая в некотором роде больше общее, но которое значительно отличается тем, что является частью систем типов, которые, в отличие от Scala, не имеют подтипов.
trait Graph { type Node type Edge def end1(e: Edge): Node def end2(e: Edge): Node def nodes: Set[Node] def edges: Set[Edge] }
где-то еще мы можем статически гарантировать, что мы не смешиваем узлы из двух разных графиков, например:
def shortestPath(g: Graph)(n1: g.Node, n2: g.Node) = ...
конечно, это уже работало, если определено внутри
Graph
, но сказать, что мы не можем изменитьGraph
и пишут расширение "pimp my library" для него.о втором вопросе: типы, включенные этой функцией, являются далеко слабее, чем полные зависимые типы (см. зависимо типизированное программирование в Agda для аромата этого.) Я не думаю, что видел аналогию раньше.
эта новая функция необходима, когда бабстрактные члены типа используются вместо параметров типа. Если используются параметры типа, то семейный полиморфизм зависимость типа может быть выражена в последней и некоторых более старых версиях Scala, как в следующем упрощенном примере.
trait C[A] def f[M](a: C[M], b: M) = b class C1 extends C[Int] class C2 extends C[String] f(new C1, 0) res0: Int = 0 f(new C2, "") res1: java.lang.String = f(new C1, "") error: type mismatch; found : C1 required: C[Any] f(new C1, "") ^
Я разработка модели для взаимодействия формы декларативного программирования с состоянием окружающей среды. Детали здесь не актуальны (например, подробности о обратных вызовах и концептуальном сходстве с моделью актора в сочетании с сериализатором).
соответствующая проблема заключается в том, что значения состояния хранятся в хэш-карте и ссылаются на значение хэш-ключа. Функции вводят неизменяемые аргументы, которые являются значениями из среды, могут вызывать другие такие функции и записывать состояние окружающей среды. Но функции - это нельзя читать значения из окружения (таким образом, внутренний код функции не зависит от порядка изменения состояния и, следовательно, остается декларативным в этом смысле). Как ввести это в Scala?
класс среды должен иметь перегруженный метод, который вводит такую функцию для вызова и вводит хэш-ключи аргументов функции. Таким образом, этот метод может вызвать функцию с нужными значениями из хэш-карты, без предоставления публичного доступа для чтения значений (таким образом, по мере необходимости, отказывая функциям в возможности чтения значений из среды).
но если эти хэш-ключи являются строками или целочисленными хэш-значениями, статический тип элемента хэш-карты type включает в себя к любому или AnyRef (код хэш-карты не показан ниже), и таким образом может произойти несоответствие времени выполнения, т. е. можно было бы поместить любой тип значения в хэш-карту для данного хэша ключ.
trait Env { ... def callit[A](func: Env => Any => A, arg1key: String): A def callit[A](func: Env => Any => Any => A, arg1key: String, arg2key: String): A }
хотя я не проверял следующее, теоретически я могу получить хэш-ключи от имен классов во время выполнения, используя
classOf
, поэтому хэш-ключ - это имя класса вместо строки (используя обратные палочки Scala для вставки строки в имя класса).таким образом, статический безопасность типов.trait DependentHashKey { type ValueType } trait `the hash key string` extends DependentHashKey { type ValueType <: SomeType }
def callit[A](arg1key: DependentHashKey)(func: Env => arg1key.ValueType => A): A