2015-08-19 11 views
8

Obecnie koduję w scala i uważam siebie za początkującego. Mam 3 klasy, które są poza moją kontrolą, co oznacza, że ​​nie mogę ich zmienić.Unikaj powielania kodu za pomocą pisania na klawiaturze w 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 } 
} 

To jest uproszczony przykład rzeczywisty kod jest bardziej skomplikowana i Klasy A, B mają wiele innych podobnych metod.

potrzebne do wywołania metody M dla przypadków klasy A, B

Oczywistym rozwiązaniem jest:

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

ale obejmuje kod powielania. Starałem się go rozwiązać z kaczki typowania i do tej pory mój najlepszy podejście do tematu to:

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) 

ale pojawiają się błędy typu wnioskowania jak:

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) 
^ 

Czy problem ten można rozwiązać w elegancki sposób z minimalną magią?

Odpowiedz

11

Masz kilka opcji. Jednym z nich jest zapewnienie parametry typu jawnie:

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

Jeśli metoda m zawsze zwraca wartość typu, który jest zdefiniowana, możesz pomóc typ wnioskowania przez kodującego ten fakt w swojej 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] 

można również pominąć alias typ:

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] 

bym zdecydowanie wskazują użyciu klasy typ, mimo, że to trochę Syntac tic napowietrznych ale znacznie czystsze:

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) 

A potem:

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

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

Nie odbijający dostępu i używasz szeroko rozumianego idiom.

+1

Dlaczego zdecydowałeś się na wpisanie typu "Out" zamiast drugiego parametru? Ponieważ jest funkcjonalnie zdefiniowany przez parametr pierwszego typu, a to pomaga w wnioskowaniu typu? – ziggystar

+0

@ziggystar Dokładnie. Nie potrzebujesz w ten sposób dodatkowego parametru typu na 'fill'. –

1

Możesz użyć typeclass, ale szczerze mówiąc w tym przypadku chciałbym tylko dopasować wzór, jeśli naprawdę nie ma typowej supertypy A i 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) 
} 

Jeśli tak naprawdę są tylko dwa, po prostu użyj dopasowywania wzorców.

Powiązane problemy