2015-04-10 18 views
8

ho qualche codice come questo:Come posso scrivere una funzione con un tipo di ritorno polimorfico basato sull'argomento tipo del suo parametro tipo?

sealed trait Foo[A] { 
    def value: A 
} 
case class StringFoo(value: String) extends Foo[String] 
case class IntFoo(value: Int) extends Foo[Int] 

mi piacerebbe avere una funzione in grado di utilizzare il tipo di A dato parametro tipo di un sottotipo.

// Hypothetical invocation 
val i: Int = dostuff[IntFoo](param) 
val s: String = dostuff[StringFoo](param) 

io non riesco a capire come dichiarare dostuff in un modo che funziona. La cosa più vicina che posso capire è

def dostuff[B <: Foo[A]](p: Param): A 

Ma questo non funziona perché A è definito in quella posizione. Posso fare qualcosa di simile

def dostuff[A, B <: Foo[A]](p: Param): A 

Ma poi devo invocarlo come dostuff[String, StringFoo](param) che è piuttosto brutto.

Sembra che il compilatore dovrebbe avere tutte le informazioni di cui ha bisogno per muoversi A attraverso al tipo di ritorno, come posso fare questo lavoro, sia in scala standard o con una libreria. Sono attualmente su scala 2.10 se questo influisce sulla risposta. Sono aperto a un 2.11-unica soluzione se è possibile lì, ma impossibile in 2.10

risposta

6

Come sapete, se avete un parametro di tipo Foo[A], allora si può fare il metodo generico in soli A:

def dostuff[A](p: Foo[A]): A = ??? 

Dal momento che potrebbe non essere sempre il caso, possiamo provare a utilizzare un parametro implicito che può esprimere la relazione tra A e B. Poiché non possiamo applicare solo alcuni dei parametri generici a una chiamata di metodo (l'inferenza dei parametri generici è tutto o niente), dobbiamo suddividerla in 2 chiamate. Questa è un'opzione:

case class Stuff[B <: Foo[_]]() { 
    def get[A](p: Param)(implicit ev: B => Foo[A]): A = ??? 
} 

È possibile controllare i tipi nella REPL:

:t Stuff[IntFoo].get(new Param) //Int 
:t Stuff[StringFoo].get(new Param) //String 

Un'altra opzione lungo le stesse linee, ma utilizzando una classe anonima, è:

def stuff[B <: Foo[_]] = new { 
    def apply[A](p: Param)(implicit ev: B <:< Foo[A]): A = ??? 
} 

:t stuff[IntFoo](new Param) //Int 

Qui, ho usato apply anziché get, quindi puoi applicare il metodo in modo più naturale. Inoltre, come suggerito nel tuo commento, qui ho usato <:< nel tipo di prova. Per coloro che desiderano saperne di più su questo tipo di vincolo di tipo generalizzato , è possibile leggere più here.

Si potrebbe anche prendere in considerazione l'utilizzo di membri di tipo astratto invece di parametri generici qui. Quando si combatte con l'inferenza di tipo generico, questo spesso fornisce una soluzione elegante. Puoi leggere di più sui membri di tipo astratto e il loro rapporto con i generici here.

+0

Questo è quello che sono andato con, ma invece di 'ev: B => Foo [A]' Ho usato 'ev: B <: Daenyth

5

Un'altra opzione è quella di utilizzare i soci Type:

sealed trait Foo { 
    type Value 
    def value: Value 
} 

case class StringFoo(value: String) extends Foo { type Value = String } 
case class IntFoo(value: Int) extends Foo { type Value = Int } 

def dostuff[B <: Foo](p: Any): B#Value = ??? 

// Hypothetical invocation 
val i: Int = dostuff[IntFoo](param) 
val s: String = dostuff[StringFoo](param) 

Si noti che entrambe le soluzioni funzionano principalmente intorno alla restrizione sintattica a Scala, che non si può risolvere un parametro tipo di una lista ed avere il compilatore dedurre l'altro.

+0

Questa è in effetti * la * soluzione corretta. Poiché l'autore della domanda desidera ottenere un tipo associato a un altro tipo, si presume implicitamente che tale relazione sia univoca: per ogni tipo di sorgente esiste un tipo di destinazione. Questo è esattamente ciò che i membri del tipo astratto/tipi associati sono per. –

+0

Ho provato questo, ma per il mio caso d'uso particolare ricevevo errori che sembravano fare una digitazione dipendente dal percorso, con il valore di uno StringFoo diverso dal valore di un altro – Daenyth