2013-09-01 9 views
5

Ho letto diversi articoli che esprimono che i tipi astratti devono essere utilizzati per ottenere il polimorfismo con f-bound in Scala. Questo è principalmente per alleviare i problemi di inferenza di tipo, ma anche per rimuovere la crescita quadratica che i parametri di tipo sembrano introdurre quando si definiscono i tipi ricorsivi.Polimorfismo F-Bounded con tipi astratti in Scala

Questi sono definiti come in:

trait EventSourced[E] { 
    self => 

    type FBound <: EventSourced[E] { type FBound <: self.FBound } 

    def apply(event: E): FBound 
} 

Tuttavia, questo sembra introdurre due problemi:

1) Ogni volta che un utente desidera fare riferimento a un oggetto di questo tipo, devono anche fare riferimento il parametro di tipo FBound. Questo si sente come un odore di codice:

def mapToSomething[ES <: EventSourced[E], E](eventSourced: ES#FBound): Something[ES, E] = ... 

2) Il compilatore è ora in grado di dedurre parametri di tipo per i metodi, come il precedente, non riuscendo con il messaggio:

Type mismatch, expected: NotInferredES#FBound, actual: MyImpl#FBound 

qualcuno là fuori con un implementazione riuscita del polimorfismo delimitato da f nella loro soluzione, per cui il compilatore è ancora in grado di inferire i tipi?

+0

Il collezioni libreria Scala utilizza F-delimitata polimorfismo con successo senza problemi. Utilizza i parametri del tipo anziché i membri del tipo, potresti provare una soluzione basata su questo. – wingedsubmariner

+0

Per favore, puoi darmi un esempio da guardare, cioè quali parti della biblioteca fanno questo? –

+0

Vedere [Elenco] (http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.List) nella libreria standard. Si noti il ​​polimorfismo con F-bounded utilizzato nella sua ereditarietà di GenericTraversableTemplate e LinearSeqOptimized. – wingedsubmariner

risposta

3

allora ho capito che il polimorfismo f-delimitata dovrebbe essere evitato nella maggior parte dei casi - o meglio - di solito c'è un progetto alternativo che si dovrebbe optare per. Per capire come evitarlo, abbiamo prima bisogno di sapere ciò che ci rende lo richiedono:

polimorfismo F-delimitata si verifica quando un tipo di interfaccia si aspetta importanti cambiamenti da introdurre nei tipi derivati.

Questo è evitato componendo le zone previsti dei cambiamenti invece di tentare di sostenerli attraverso l'ereditarietà. Questo viene in realtà torna a Gang of Four design pattern:

favore 'composizione di oggetti' over 'ereditarietà di classe'

- (Gang of Four, 1995)

Ad esempio:

trait Vehicle[V <: Vehicle[V, W], W] { 
    def replaceWheels(wheels: W): V 
} 

diventa:

trait Vehicle[T, W] { 
    val vehicleType: T 
    def replaceWheels(wheels: W): Vehicle[T, W] 
} 

Qui, la "modifica prevista" è il tipo di veicolo (ad esempio Bike, Car, Lorry). L'esempio precedente presupponeva che questo sarebbe stato aggiunto attraverso l'ereditarietà, richiedendo un tipo con il tipo f che rendesse impossibile l'inferenza di W per qualsiasi funzione che utilizza Veicolo. Il nuovo metodo, che utilizza la composizione, non presenta questo problema.

See: https://github.com/ljwagerfield/scala-type-inference/blob/master/README.md#avoiding-f-bounded-polymorphism

+0

> Il nuovo metodo, che utilizza la composizione, non presenta questo problema Non proprio vero. Se si desidera conservare i controlli a livello di carattere, è necessario alla fine, ad esempio 'VehicleType [V <: VehicleType]' – ayvango

-1
+0

Questo esempio utilizza i parametri di tipo e pertanto causa problemi di inferenza. Nel mio esempio, i tipi per la seguente definizione di metodo non possono essere desunti dal sito di chiamata: def doSomething [ES <: EventSourced [ES, E], E] (that: ES) –

+0

Negli esempi da Twitter o dalla libreria Scala , il parametro di tipo F-bound è sulla classe stessa, non sui metodi. Il polimorfismo F-bounded consente ai metodi di riferirsi al tipo di 'this', il tipo esatto che finisce per essere in sottoclassi. Prendiamo ad esempio la definizione di 'map' in' TraversableLike' ereditato in 'List',' def map [B, That] (f: A => B) (implicito bf: CanBuildFrom [Repr, B, That]): Quello '. Repr è il parametro di tipo F-Bound, ed è List [A] all'interno di List, ma il codice in TraversableLike può essere scritto senza alcuna conoscenza di 'List'. – wingedsubmariner

+0

Sì, penso che alla fine ho bisogno di ripensare un po 'del mio design. Mi sembra che il polimorfismo delimitato da f dovrebbe essere usato solo a livello di definizione del tipo, quando si eredita da un supertipo delimitato da f. Tuttavia, nel mio codice, ci sono diversi casi in cui viene utilizzato a livello di metodo ... Sento che questo deve cambiare. –

-1

Probabilmente mi manca qualcosa con la seguente implementazione.

trait EventSourced[E] { 
    self => 

    type FBound <: EventSourced[E] { type FBound <: self.FBound } 

    def apply(event: E): FBound 
} 

trait Something[ES, E] 

def mapToSomething[E](
    eventSourced: ES forSome { 
    type ES <: EventSourced[E] 
    }): Something[eventSourced.type, E] = ??? 

class Test extends EventSourced[Boolean] { 
    type FBound = Test 
    def apply(event:Boolean):FBound = ??? 
} 

val x:Test = ??? 

mapToSomething(x)