Более или менее любое использование членов (т. Е. Вложенных) типов может вызвать потребность в зависимых типах методов. В частности, я утверждаю, что без зависимых типов методов классический паттерн торта больше похож на антишаблон.
Так в чем проблема? Вложенные типы в Scala зависят от их включающего экземпляра. Следовательно, при отсутствии зависимых типов методов попытки использовать их вне этого экземпляра могут быть удручающе трудными. Это может превратить дизайн, который поначалу казался элегантным и привлекательным, в чудовищно жесткие и трудно поддающиеся рефакторингу.
Я проиллюстрирую это упражнением, которое я даю во время моего учебного курса Advanced 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
, и ничего больше.
Это, несомненно, желательное свойство, но предположим, мы хотим переместить этот тестовый код в другой исходный файл? С зависимыми типами методов тривиально легко переопределить эти методы вне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)
После непродолжительной борьбы с этим вы, вероятно, поймете, почему я (или, может быть, это был Дэвид Макивер, мы не можем вспомнить, кто из нас придумал этот термин) называю это Пекарней Судьбы.
Изменить: консенсус заключается в том, что Bakery of Doom была монетой Дэвида Макивера ...
В качестве бонуса: форма зависимых типов в Scala в целом (и зависимые типы методов как ее часть) была вдохновлена языком программирования Beta ... они естественным образом возникают из последовательной семантики вложенности Beta. Я не знаю ни одного другого, хотя бы отчасти основного языка программирования, который имел бы зависимые типы в этой форме. Такие языки, как Coq, Cayenne, Epigram и Agda, имеют другую форму зависимой типизации, которая в некотором роде является более общей, но значительно отличается тем, что является частью систем типов, которые, в отличие от Scala, не имеют подтипов.