2015-08-09 9 views
15

Diciamo che ho un tratto marcatore vuoto denominato Marker e alcune funzioni con parametri di tipo legati da Marker:Perché questa funzione di Scala viene compilata quando l'argomento non è conforme al vincolo di tipo?

trait Marker 

object Marker { 
    def works[M <: Marker](m:M):M = m 
    def doesntWork[M <: Marker](f:M => String):String = "doesn't matter" 
} 

La prima funzione funziona come mi aspetto. Cioè, se si passa un parametro che non è un Marker, quindi il codice non compila:

scala> works("a string") 
<console>:14: error: inferred type arguments [String] do not conform to method works's type parameter bounds [M <: com.joescii.Marker] 
     works("a string") 
    ^
<console>:14: error: type mismatch; 
found : String("a string") 
required: M 
     works("a string") 
      ^

Tuttavia, sono in grado di passare un parametro per la seconda funzione che non è conforme alla Marker. In particolare, posso passare una funzione di tipo String => String e il codice compila felicemente e corre:

scala> doesntWork((str:String) => "a string") 
res1: String = doesn't matter 

mi si aspetterebbe questa chiamata a doesntWork a fallire per la compilazione. Qualcuno può spiegarmi perché compila e come posso modificare la firma della funzione per evitare che i tipi controllino in questi casi?

Informazioni complete: l'esempio sopraindicato è una versione semplificata di this outstanding issue for lift-ng.

risposta

4

M => String è in realtà un Function1[M, String]. Se guardate la definizione:

trait Function1[-T1, +R] 

Così M diventa contravariant, il che significa che per M1 >: M2, Function1[M1, String] <: Function1[M2, String], diciamo M1 = Any poi Function1[Any, String] <: Function1[Marker, String].

e l'immissione di doesntWork-f è anche contravariant, il che significa che è possibile passare qualcosa di più piccolo M => String, e come ho appena dimostrato, Any => String è più piccolo Marker => String, quindi passess completamente bene.

È possibile anche passare String => String a causa della vostra [M <: Marker], che alla fine induce compilatore di interpretare M come Nothing, quindi, anche String => String diventa più grande di M => String.


per risolvere il problema, basta introdurre involucro, che renderà il vostro tipo di invariante:

scala> case class F[M](f: M => String) 
defined class F 

scala> def doesntWork[M <: Marker](f:F[M]):String = "doesn't matter" 
doesntWork: [M <: Marker](f: F[M])String 

scala> doesntWork(F((str: String) => "a string")) 
<console>:18: error: inferred type arguments [String] do not conform to method doesntWork's type parameter bounds [M <: Marker] 
       doesntWork(F((str: String) => "a string")) 
      ^
<console>:18: error: type mismatch; 
found : F[String] 
required: F[M] 
       doesntWork(F((str: String) => "a string")) 
         ^
scala> doesntWork(F((str: Any) => "a string")) 
<console>:18: error: inferred type arguments [Any] do not conform to method doesntWork's type parameter bounds [M <: Marker] 
       doesntWork(F((str: Any) => "a string")) 
      ^
<console>:18: error: type mismatch; 
found : F[Any] 
required: F[M] 
Note: Any >: M, but class F is invariant in type M. 
You may wish to define M as -M instead. (SLS 4.5) 
       doesntWork(F((str: Any) => "a string")) 

scala> doesntWork(F((str: Marker) => "a string")) 
res21: String = doesn't matter 

scala> trait Marker2 extends Marker 
defined trait Marker2 

scala> doesntWork(F((str: Marker) => "a string")) 
res22: String = doesn't matter 

scala> doesntWork(F((str: Marker2) => "a string")) 
res23: String = doesn't matter 

Di solito cattivo di raccomandare tali conversioni implicite, ma sembra che vada bene qui (se non lo farà un uso eccessivo F):

scala> implicit def wrap[M](f: M => String) = F(f) 
warning: there was one feature warning; re-run with -feature for details 
wrap: [M](f: M => String)F[M] 

scala> doesntWork((str: Marker) => "a string") 
res27: String = doesn't matter 

scala> doesntWork((str: String) => "a string") 
<console>:21: error: inferred type arguments [String] do not conform to method doesntWork's type parameter bounds [M <: Marker] 
       doesntWork((str: String) => "a string") 
      ^
<console>:21: error: type mismatch; 
found : F[String] 
required: F[M] 
       doesntWork((str: String) => "a string") 
            ^
+0

Ma l'OP sta passando '(str: String) =>" una stringa "' come argomento - questo non è di tipo 'Any => String' (né alcun sottotipo di esso)? – chi

+0

è sbagliato, verifica la risposta di @larsrh – kosii

+0

Questo è comunque utile. Non stavo pensando al fatto che 'M => String' è in realtà' Function1 [M, String] 'che potrebbe aver messo in moto la mia bandiera mentale per considerare la contravarianza. Conosco il concetto, ma sicuramente non lo so _know_ it. :) – joescii

6

Il codice viene compilato a causa della controvarianza. Lo puoi vedere dando esplicitamente il parametro di tipo inferito:

doesntWork[Nothing]((str: String) => "a string") 

Questo è un problema generale. Esistono varie tecniche per ovviare a questo problema, ma in genere si riducono a limitare T ad essere un'istanza di una classe di tipi.

+1

Grazie per aver risposto qui e twittato su di me, Sembra che ho semplicemente dovuto dichiararlo come 'doesntWork [M>: Marker] ...'. – joescii

+0

Sono stato corretto. Lanciare la parentesi angolare NON è il trucco magico. Fa fallire anche funzioni valide. Ad esempio, se creo 'case class Container (str: String) estende Marker' questo caso valido non viene compilato:' doesntWork ((obj: Container) => "stuff") ' – joescii

+0

@joescii L'utilizzo avanzato di generici mi ha causato Nient'altro che dolore in passato, ma una volta che ne hai fatto esperienza, posso solo sperare che migliori. – mucaho

Problemi correlati