Mehr oder weniger kann jede Verwendung von Elementtypen (dh verschachtelten Typen) zu einem Bedarf an abhängigen Methodentypen führen. Insbesondere behaupte ich, dass das klassische Kuchenmuster ohne abhängige Methodentypen eher ein Anti-Muster ist.
Also, was ist das Problem? Verschachtelte Typen in Scala hängen von ihrer umschließenden Instanz ab. Wenn keine abhängigen Methodentypen vorhanden sind, können Versuche, sie außerhalb dieser Instanz zu verwenden, frustrierend schwierig sein. Dies kann Designs, die anfangs elegant und ansprechend erscheinen, in Monstrositäten verwandeln, die albtraumhaft starr und schwer zu überarbeiten sind.
Ich werde das veranschaulichen mit einer Übung , die ich während meiner geben Erweiterte Scala Trainingskurs ,
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
}
Es ist ein Beispiel für das klassische Kuchenmuster: Wir haben eine Familie von Abstraktionen, die schrittweise durch eine Hierarchie verfeinert werden ( ResourceManager
/ Resource
werden durch FileManager
/ verfeinert, File
die wiederum durch NetworkFileManager
/ verfeinert werden RemoteFile
). Es ist ein Spielzeugbeispiel, aber das Muster ist real: Es wird im gesamten Scala-Compiler verwendet und wurde im Scala Eclipse-Plugin ausgiebig verwendet.
Hier ist ein Beispiel für die verwendete Abstraktion:
val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)
Beachten Sie, dass die Pfadabhängigkeit bedeutet, dass der Compiler garantiert, dass die Methoden testHash
und nur mit Argumenten aufgerufen werden können, die ihr entsprechen, d. H. es ist eigen und sonst nichts.testDuplicates
NetworkFileManager
RemoteFiles
Das ist zweifellos eine wünschenswerte Eigenschaft, aber nehmen wir an, wir wollten diesen Testcode in eine andere Quelldatei verschieben? Mit abhängigen Methodentypen ist es trivial einfach, diese Methoden außerhalb der ResourceManager
Hierarchie neu zu definieren.
def testHash4(rm : ResourceManager)(r : rm.Resource) =
assert(r.hash == "9e47088d")
def testDuplicates4(rm : ResourceManager)(r : rm.Resource) =
assert(r.duplicates(r))
Beachten Sie hier die Verwendung abhängiger Methodentypen: Der Typ des zweiten Arguments ( rm.Resource
) hängt vom Wert des ersten Arguments ( rm
) ab.
Es ist möglich, dies ohne abhängige Methodentypen zu tun, aber es ist äußerst umständlich und der Mechanismus ist ziemlich unintuitiv: Ich unterrichte diesen Kurs seit fast zwei Jahren, und in dieser Zeit hat niemand eine funktionierende Lösung ohne Aufforderung gefunden.
Probieren Sie es aus ...
// 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)
Nach kurzer Zeit werden Sie wahrscheinlich feststellen, warum ich (oder vielleicht war es David MacIver, wir können uns nicht erinnern, wer von uns den Begriff geprägt hat) dies die Bäckerei des Schicksals nenne.
Bearbeiten: Konsens ist, dass Bakery of Doom David MacIvers Münzprägung war ...
Für den Bonus: Scalas Form von abhängigen Typen im Allgemeinen (und abhängigen Methodentypen als Teil davon) wurde von der Programmiersprache Beta inspiriert ... sie ergeben sich natürlich aus der konsistenten Verschachtelungssemantik von Beta. Ich kenne keine andere, auch nur schwach verbreitete Programmiersprache, die abhängige Typen in dieser Form hat. Sprachen wie Coq, Cayenne, Epigram und Agda haben eine andere Form der abhängigen Typisierung, die in gewisser Weise allgemeiner ist, sich jedoch erheblich dadurch unterscheidet, dass sie Teil von Typsystemen ist, die im Gegensatz zu Scala keine Subtypisierung haben.