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