2015-08-19 5 views
8

Attualmente sto codificando in scala e considero me stesso un principiante. Ho 3 classi fuori dal mio controllo, il che significa che non posso cambiarle.Evita la duplicazione del codice utilizzando la digitazione anatra in scala

class P 

class A { 
    def m(p: P): A = { println("A.m"); this } 
} 

class B { 
    def m(p: P): B = { println("B.m"); this } 
} 

Questo è un esempio semplificato il codice effettivo è più complicato e classi A, B sono molti altri metodi simili.

Ho bisogno di chiamare il metodo m per le istanze delle classi A, B

La soluzione più ovvia è:

def fill(ab: AnyRef, p: P): Unit = { 
    ab match { 
    case a: A => a.m(p) 
    case b: B => b.m(p) 
    } 
} 

ma che coinvolge la duplicazione del codice. Ho cercato di risolverlo con la tipizzazione anatra e finora la mia migliore assumere il soggetto è questo:

type WithM[T] = { def m(p: P): T } 

def fill[S, T <: WithM[S]](ab: T, p: P): S = 
    ab.m(p) 

fill(new A, new P) 

ma ottengo gli errori di inferenza di tipo simile:

Error:(18, 5) inferred type arguments [Nothing,A] do not conform to method fill's type parameter bounds [S,T <: Test.WithM[S]] 
fill(new A, new P) 
^ 

può questo problema essere risolto in un elegante modo con la magia minima?

risposta

11

Hai alcune opzioni. Uno è quello di fornire i parametri di tipo esplicitamente:

scala> fill[A, A](new A, new P) 
A.m 
res1: A = [email protected] 

Se il metodo m restituisce sempre un valore del tipo che è definita su, si può aiutare il tipo di inferenza codificando tale circostanza nella vostra fill:

scala> def fill[T <: WithM[T]](o: T, p: P): T = o.m(p) 
fill: [T <: WithM[T]](o: T, p: P)T 

scala> fill(new A, new P) 
A.m 
res2: A = [email protected] 

si può anche saltare il tipo di alias:

scala> def fill[S](o: { def m(o: P): S }, p: P): S = o.m(p) 
fill: [S](o: AnyRef{def m(o: P): S}, p: P)S 

scala> fill(new A, new P) 
A.m 
res3: A = [email protected] 

avrei fortemente suggerisco di usare una classe tipo, anche se: è un po 'di Syntac spese generali tic ma molto più pulito:

trait HasM[T] { 
    type Out 

    def apply(t: T, p: P): Out 
} 

object HasM { 
    type Aux[T, Out0] = HasM[T] { type Out = Out0 } 

    implicit def AHasM: Aux[A, A] = new HasM[A] { 
    type Out = A 
    def apply(t: A, p: P): A = t.m(p) 
    } 

    implicit def BHasM: Aux[B, B] = new HasM[B] { 
    type Out = B 
    def apply(t: B, p: P): B = t.m(p) 
    } 
} 

def fill[T](t: T, p: P)(implicit hm: HasM[T]): hm.Out = hm(t, p) 

E poi:

scala> fill(new A, new P) 
A.m 
res4: A = [email protected] 

scala> fill(new B, new P) 
B.m 
res5: B = [email protected] 

Nessun accesso riflessivo e si sta utilizzando un linguaggio ampiamente capito.

+1

Perché hai scelto di digitare "Out" come membro del tipo anziché come secondo parametro? Perché è definito dal primo parametro di tipo funzionale e questo aiuta a digitare l'inferenza? – ziggystar

+0

@ziggystar Esattamente. Non hai bisogno di un parametro di tipo extra su 'fill' in questo modo. –

1

È possibile utilizzare una classe di caratteri, ma in questo caso sinceramente modellerei la corrispondenza se non esiste il supertipo tipico di A e B.

trait POps[T] { 
    def m(t: T, p: P): T 
} 

object POps { 
    def apply[T : POps] = implicitly[POps[T]] 
} 

object A { 
    implicit val aPops: POps[A] = new POps[A] { 
    def m(t: A, p: P) = t.m(p) 
    } 
} 

object B { 
    implicit val bPops: POps[B] = new POps[B] { 
    def m(t: B, p: P) = t.m(p) 
    } 
} 

def fill[M : POps](o: M, p: P): Unit = { 
    POps[M].m(o, p) 
} 

Se ce ne sono solo due, utilizzare solo la corrispondenza del modello.

Problemi correlati