2015-08-22 9 views
5

ich den Abschnitt 20.7 des Buches Programming in Scala zu lesen und ich habe mich gefragt, warum während dieser Code kompiliert:Scala Typen: Klasse A ist nicht gleich dem T wobei T: Typ T = A

class Food 
class Fish extends Food 
class Grass extends Food 

abstract class Animal { 
    type SuitableFood <: Food 
    def eat(food: SuitableFood) 
} 


class Cow extends Animal { 
    type SuitableFood = Grass 
    override def eat(food: Grass) {} 
} 


val bessy: Animal = new Cow 

bessy eat (new bessy.SuitableFood) 

Dieser Code funktioniert nicht (der Rest des Codes ist das gleiche wie vorher, nur die letzte Zeile Änderungen):

bessy eat (new Grass) 

Und soweit ich die Art von Grass verstehen ist das gleiche von Cow.SuitableFood.

Auch habe ich eine andere Frage zu diesem Beispiel:

Wenn bessy vom Typ Tier ist, wie kann der Compiler weiß, dass es eine Art SuitableFood braucht -> Grass statt eine Art Food? Weil versuchen, eine neue Nahrung zur Verfügung zu stellen, gibt mir einen Compiler-Fehler von Typ stimmt nicht überein, aber die Klasse Tier braucht eine Art Nahrung und die Art der bessy explizit definiert: Animal

+0

Vorschlag: Fügen Sie dieser Frage das Tag _path-dependent-type_ hinzu. Das könnte eine Antwort von jemandem bekommen, der mehr über genau diese Schwierigkeit weiß. (Ich kämpfe immer noch mit pfadabhängigen Typen.) –

+0

@BenKovitz Hinzugefügt, danke. – vicaba

Antwort

10

Es ist, weil bessieAnimal erklärt wird, statt Cow. bessie.SuitableFood ist ein "pfadabhängiger Typ" (siehe unten).

Versuchen Sie folgendes:

val clarabelle: Cow = new Cow 

clarabelle eat (new Grass) 

Das funktioniert, weil der Compiler kann ableiten, dass clarabelle.SuitableFood = Grass von clarabelle ‚s Typ deklariert.

Seit bessie ist Animal erklärt, nicht Cow, kann der Compiler nicht sicher ableiten, dass bessie.SuitableFood = Grass. * Wenn Sie new bessie.SuitableFood sagen, der Compiler Code auf dem tatsächlichen bessie Objekt aussehen erzeugt und erzeugen eine neue Instanz des entsprechenden Typs. bessie.SuitableFood ist ein "pfadabhängiger Typ": Der "path" (der bessie. Teil), der zum letzten Bezeichner (SuitableFood) führt, ist tatsächlich Teil des Typs. Auf diese Weise können Sie eine benutzerdefinierte Version eines Typs für jedes einzelne Objekt derselben Klasse erstellen.


* Na ja, eigentlich, ich glaube, dass, wenn der Compiler ein wenig schlauer waren, ist es, dass bessie.SuitableFood = Grass ableiten könnte, da bessie ein val, kein var, und wird daher nicht seine Art ändern. Mit anderen Worten, der Compiler sollte wissen, dass, obwohl bessie als Animal deklariert ist, sie wirklich eine Cow ist. Vielleicht wird sich eine zukünftige Version des Compilers dieses Wissen zunutze machen, und vielleicht gibt es einen guten Grund, warum das keine gute Idee wäre, die jemand, der mehr Experte ist als ich, uns mitteilen wird. (Postscript: Einer hat es gerade getan! Siehe Travis Browns Kommentar unten.)

+7

Über Ihre Fußnote: Wenn Sie eine Typ-Annotation auf etwas setzen, wird der Compiler sie als diesen Typ behandeln, auch wenn er etwas Spezifischeres ableiten könnte. Sie müssten eine Typverfeinerung wie 'val bessy: Animal {type SuitableFood = Grass}' verwenden, wenn Sie den Typ member, nicht aber den Subtype verfolgen möchten. –

1

Zum zweiten Teil Ihrer Frage: Das tut es nicht. Animal spezifiziert nicht, dass sein Essen Food ist, aber einige Untertypen von Food. Würde der Compiler das akzeptieren, würde Code wie dein Beispiel kompilieren, und das auch falsch. Der Compiler weiß nicht, dass der notwendige Subtyp Grass ist (weshalb eat(new Grass) auch nicht funktioniert), er weiß nur, dass es einige Nahrungsmittel gibt, die Ihre Kuh nicht essen kann und ist vorsichtig damit.

1

Ich glaube, bessy eat (new bessy.SuitableFood) Compiling ist ein Bug (der in 2.11 behoben wurde).Da ein anderer Subtyp von Animal einen SuitableFood haben könnte, für den new keinen Sinn ergibt, z.B. type SuitableFood = Food oder sogar type SuitableFood = Food with Int (Food with Int ist ein vollkommen netter Subtyp von Food!).