Більш-менш будь-яке використання типів членів (тобто вкладених) може породжувати потребу в залежних типах методів. Зокрема, я стверджую, що без залежних типів методу класичний зразок торта ближче до антитіла.
То в чому проблема? Вкладені типи в 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
, і більше нічого.
Це, безперечно, бажана властивість, але припустимо, що ми хотіли перенести цей тестовий код в інший вихідний файл? Із залежними типами методів надзвичайно легко переосмислити ці методи поза 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, не мають підтипів.