Più o meno qualsiasi uso di tipi di membri (cioè nidificati) può far sorgere la necessità di tipi di metodi dipendenti. In particolare, sostengo che senza tipi di metodo dipendenti il classico schema a torta è più vicino all'essere un anti-schema.
Allora, qual'è il problema? I tipi nidificati in Scala dipendono dall'istanza che li racchiude. Di conseguenza, in assenza di tipi di metodi dipendenti, i tentativi di utilizzarli al di fuori di tale istanza possono essere frustranti. Questo può trasformare disegni che inizialmente sembrano eleganti e accattivanti in mostruosità che sono incredibilmente rigide e difficili da refactoring.
Illustrerò che con un esercizio che do durante il mio corso di formazione alla Scala Avanzata ,
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
}
È un esempio del modello classico di torta: abbiamo una famiglia di astrazioni che vengono gradualmente raffinate attraverso un'erarchia ( ResourceManager
/ Resource
sono raffinate da FileManager
/ File
che a loro volta sono raffinate da NetworkFileManager
/RemoteFile
). È un esempio di giocattolo, ma il modello è reale: è usato in tutto il compilatore Scala ed è stato ampiamente utilizzato nel plugin Scala Eclipse.
Ecco un esempio dell'astrazione in uso,
val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)
Si noti che la dipendenza del percorso significa che il compilatore garantirà che i metodi testHash
e testDuplicates
on NetworkFileManager
possano essere chiamati solo con argomenti che corrispondono ad esso, ad es. è proprio RemoteFiles
e nient'altro.
È innegabilmente una proprietà desiderabile, ma supponiamo di voler spostare questo codice di prova in un altro file sorgente? Con i tipi di metodi dipendenti è banalmente facile ridefinire quei metodi al di fuori della ResourceManager
gerarchia,
def testHash4(rm : ResourceManager)(r : rm.Resource) =
assert(r.hash == "9e47088d")
def testDuplicates4(rm : ResourceManager)(r : rm.Resource) =
assert(r.duplicates(r))
Nota qui gli usi dei tipi di metodi dipendenti: il tipo del secondo argomento ( rm.Resource
) dipende dal valore del primo argomento ( rm
).
È possibile farlo senza tipi di metodi dipendenti, ma è estremamente imbarazzante e il meccanismo non è abbastanza intuitivo: insegno questo corso da quasi due anni ormai, e in quel momento nessuno ha trovato una soluzione funzionante senza impegno.
Provalo tu stesso ...
// 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)
Dopo un po 'di fatica, probabilmente scoprirai perché io (o forse era David MacIver, non riusciamo a ricordare chi di noi ha coniato il termine) lo chiamo il Panificio del Destino.
Modifica: consenso è che Bakery of Doom è stato il conio di David MacIver ...
Per il vantaggio: la forma di Scala di tipi dipendenti in generale (e tipi di metodi dipendenti come parte di esso) è stata ispirata dal linguaggio di programmazione Beta ... derivano naturalmente dalla semantica di annidamento costante di Beta. Non conosco nessun altro linguaggio di programmazione nemmeno debolmente tradizionale che abbia tipi dipendenti in questa forma. Lingue come Coq, Cayenne, Epigram e Agda hanno una diversa forma di tipizzazione dipendente che è in qualche modo più generale, ma che differisce in modo significativo essendo parte di sistemi di tipi che, a differenza di Scala, non hanno sottotitoli.