Plus ou moins toute utilisation de types membres (c'est-à-dire imbriqués) peut donner lieu à un besoin de types de méthodes dépendantes. En particulier, je maintiens que sans types de méthode dépendants, le modèle de gâteau classique est plus proche d'un anti-modèle.
Donc quel est le problème? Les types imbriqués dans Scala dépendent de leur instance englobante. Par conséquent, en l'absence de types de méthodes dépendantes, les tentatives de les utiliser en dehors de cette instance peuvent être extrêmement difficiles. Cela peut transformer des designs qui semblent initialement élégants et attrayants en monstruosités qui sont d'une rigidité cauchemardesque et difficiles à refactoriser.
Je vais illustrer cela avec un exercice que je donne pendant mon cours de formation 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
}
C'est un exemple du modèle de gâteau classique: nous avons une famille d'abstractions qui sont progressivement affinées à travers une hiérarchie ( ResourceManager
/ Resource
sont raffinées par FileManager
/ File
qui sont à leur tour affinées par NetworkFileManager
/ RemoteFile
). C'est un exemple de jouet, mais le modèle est réel: il est utilisé dans tout le compilateur Scala et a été largement utilisé dans le plugin Scala Eclipse.
Voici un exemple de l'abstraction utilisée,
val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)
Notez que la dépendance de chemin signifie que le compilateur garantira que les méthodes testHash
et on ne peuvent être appelées qu'avec des arguments qui y correspondent, c'est-à-dire. c'est propre , et rien d'autre.testDuplicates
NetworkFileManager
RemoteFiles
C'est indéniablement une propriété souhaitable, mais supposons que nous voulions déplacer ce code de test vers un autre fichier source? Avec les types de méthodes dépendantes, il est très facile de redéfinir ces méthodes en dehors de la ResourceManager
hiérarchie,
def testHash4(rm : ResourceManager)(r : rm.Resource) =
assert(r.hash == "9e47088d")
def testDuplicates4(rm : ResourceManager)(r : rm.Resource) =
assert(r.duplicates(r))
Notez ici les utilisations des types de méthode dépendants: le type du deuxième argument ( rm.Resource
) dépend de la valeur du premier argument ( rm
).
Il est possible de le faire sans types de méthodes dépendantes, mais c'est extrêmement gênant et le mécanisme est assez peu intuitif: j'enseigne ce cours depuis près de deux ans maintenant, et pendant ce temps, personne n'a trouvé une solution de travail sans invitation.
Essayez-le par vous-même ...
// 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)
Après un court moment, vous découvrirez probablement pourquoi moi (ou peut-être que c'était David MacIver, nous ne pouvons pas nous rappeler lequel d'entre nous a inventé le terme) appelle cela la Bakery of Doom.
Edit: le consensus est que Bakery of Doom était la monnaie de David MacIver ...
Pour le bonus: la forme de Scala des types dépendants en général (et des types de méthodes dépendants en tant que partie de celui-ci) a été inspirée par le langage de programmation Beta ... ils proviennent naturellement de la sémantique d'imbrication cohérente de Beta. Je ne connais aucun autre langage de programmation, même faiblement courant, qui a des types dépendants sous cette forme. Des langages comme Coq, Cayenne, Epigram et Agda ont une forme différente de typage dépendant qui est à certains égards plus générale, mais qui diffère considérablement en faisant partie de systèmes de types qui, contrairement à Scala, n'ont pas de sous-typage.