2015-06-01 10 views
51

Dato un typeclass dove la selezione istanza deve essere eseguito in base al tipo di ritorno:imprevisto risoluzione implicita sulla base di inferenza da tipo di ritorno

case class Monoid[A](m0: A) // We only care about the zero here 
implicit def s[T] : Monoid[Set[T]] = Monoid(Set.empty[T]) 
implicit def l[T] : Monoid[List[T]] = Monoid(List.empty[T]) 
def mzero[A](implicit m: Monoid[A]) : A = m.m0 

Perché Scala (2.11.6) non riescono a risolvere il corretto esempio:

scala> mzero : List[Int] 
<console>:24: error: ambiguous implicit values: 
both method s of type [T]=> Monoid[Set[T]] 
and method l of type [T]=> Monoid[List[T]] 
match expected type Monoid[A] 
       mzero : List[Int] 
      ^

quando non ha problemi a trovare un implicito in base al tipo di ritorno quando si utilizza la funzione implicitamente (abbiamo ridefinire qui come i per illustrare come s analoghe sui è di mzero)

def i[A](implicit a : A) : A = a 
scala> i : Monoid[List[Int]] 
res18: Monoid[List[Int]] = Monoid(List()) 

Il Monoid[A], invece di Monoid[List[Int]] nel messaggio di errore è sconcertante.

Suppongo che molti contributori scalaz abbiano familiarità con questo problema in quanto sembra limitare la praticità dei typeclass in scala.

EDIT: Sto cercando di ottenere questo funzionamento senza dedurre l'inferenza del tipo. Altrimenti mi piacerebbe capire perché non è possibile. Se questa limitazione è documentata come un problema di Scala, non potrei trovarlo.

+4

I limiti della capacità di Scala di utilizzare i tipi di ritorno attesi per guidare l'inferenza dei parametri di tipo quando sono coinvolti impliciti sono incredibilmente confusi, quindi tutti scrivono semplicemente 'mzero [Elenco [Int]]'. –

+2

Come soluzione, puoi scappare ridefinendo 'mzero' come' def mzero [A] (implicito m: Monoid [_ <: A]): A = m.m0'. Ut mi piacerebbe andare con 'mzero [List [Int]]' come menzionato da Travis Brown. –

+0

Impressionante e sorprendente. Grazie! Alcuna spiegazione? Questo sembra rilassare il tipo e ottenere meno partite! –

risposta

1

1) Dopo aver riscritto il codice come segue:

case class Monoid[A](m0: A) // We only care about the zero here 
implicit def s[T] : Monoid[Set[T]] = Monoid(Set.empty[T]) 
implicit def l[T] : Monoid[List[T]] = Monoid(List.empty[T]) 
def mzero[A]()(implicit m: Monoid[A]) : A = m.m0 

val zero = mzero[List[Int]]() 
val zero2: List[Int] = mzero() 

allora diventa chiaro il motivo per cui che funziona così.

2) Dopo aver impostato mzero come def mzero[A]()(implicit m: Monoid[_ <: A]) : A = m.m0, è stata abilitata un'altra inferenza di tipo per risolvere il tipo esistente. Il compilatore ha ottenuto il tipo effettivo dal tipo di reso richiesto. Si può verificare con def mzero[A <: B, B]()(implicit m: Monoid[A]) : A = m.m0 se lo si desidera.

3) Naturalmente tutto quel comportamento è solo sottigliezza del compilatore e non penso che casi così parziali richiedessero veramente una comprensione profonda.