2015-08-22 11 views
5

stavo leggendo la sezione 20.7 della Programmazione libro in Scala e mi chiedevo perché mentre questo codice viene compilato:tipi Scala: Classe A non è uguale al T dove T è: tipo 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) 

questo codice non (il resto del codice è la stessa di prima, solo le ultime modifiche di linea):

bessy eat (new Grass) 

E per quanto mi pare di capire il tipo di erba è la stessa di Cow.SuitableFood.

Inoltre, ho un'altra domanda per quanto riguarda questo esempio:

Se Bessy è di tipo animale, come può il compilatore sa che ha bisogno di un tipo SuitableFood -> Erba, invece di un tipo di cibo? Perche' cercando di fornire un nuovo alimento mi dà un errore di compilazione di tipo non corrispondente, ma l'animale di classe ha bisogno di un tipo di alimentazione e il tipo di Bessy è esplicitamente definito: Animal

+0

Suggerimento: aggiungere il tag _path-dependent-type_ a questa domanda. Questo potrebbe attirare una risposta da qualcuno che sa più esattamente di questo tipo di difficoltà. (Sto ancora lottando con i tipi dipendenti da path.) –

+0

@BenKovitz Aggiunto, grazie. – vicaba

risposta

10

È perché bessie è dichiarato Animal piuttosto che Cow. bessie.SuitableFood è un "tipo dipendente dal percorso" (vedi sotto).

Prova questo:

val clarabelle: Cow = new Cow 

clarabelle eat (new Grass) 

Questo funziona perché il compilatore può dedurre checlarabelle.SuitableFood = Grass da clarabelle 's tipo dichiarato.

Dal bessie è dichiarato Animal, non Cow, il compilatore non può dedurre con certezza che bessie.SuitableFood = Grass. * Quando si dice new bessie.SuitableFood, il compilatore genera il codice a guardare l'oggetto reale bessie e generare una nuova istanza del tipo appropriato. è un "tipo dipendente dal percorso": lo "path" (la parte bessie.) che porta all'ultimo identificatore (SuitableFood) è in realtà parte del tipo. Ciò consente di avere una versione personalizzata di un tipo per ogni singolo oggetto della stessa classe.


* Beh, in realtà, penso che se il compilatore fosse un po 'più intelligente, si potrebbe dedurre che bessie.SuitableFood = Grass, dal momento che è un bessieval, non un var, e quindi non cambierà il suo tipo. In altre parole, il compilatore dovrebbe sapere che, anche se viene dichiarato Animal, è davvero una Cow. Forse una versione futura del compilatore farà uso di questa conoscenza, e forse c'è una buona ragione per cui non sarebbe una buona idea, che qualcuno più esperto di me ci dirà. (Poscritto: Uno appena fatto! Vedi il commento di Travis Brown sotto.)

+7

Informazioni sulla nota: se si inserisce un'annotazione di tipo su qualcosa, il compilatore la tratterà come quel tipo, anche se _could_ dedurrà qualcosa di più specifico. Dovresti usare un tipo di rifinitura come "val bessy: Animal {type SuitableFood = Grass}" se vuoi tenere traccia del membro del tipo ma non del sottotipo. –

1

Per quanto riguarda la seconda parte della tua domanda: non è così. Animal non specifica che il suo cibo è Food, ma alcuni sottotipo di Food. Il compilatore lo accetterebbe, codice come il tuo esempio potrebbe compilare, e in modo errato. Il compilatore non sa che il sottotipo necessario è Grass (che è il motivo per cui lo eat(new Grass) non funziona neanche), sa solo che ci sono alcuni alimenti che la tua mucca non può mangiare ed è prudente a riguardo.

1

Credo che la compilazione di bessy eat (new bessy.SuitableFood) sia un bug (corretto in 2.11).Perché un altro sottotipo di Animal potrebbe avere un SuitableFood per il quale new non ha senso, ad es. type SuitableFood = Food o anche type SuitableFood = Food with Int (Food with Int è un sottotipo perfettamente bello di Food!).