2012-04-27 11 views
6

Questo problema è sorto in un modulo che sto scrivendo, ma ho creato un caso minimale che mostra lo stesso comportamento.Perché non digitare l'inferenza funziona qui?

class Minimal[T](x : T) { 
    def doSomething = x 
} 

object Sugar { 
    type S[T] = { def doSomething : T } 
    def apply[T, X <: S[T]] (x: X) = x.doSomething 
} 

object Error { 
    val a = new Minimal(4) 
    Sugar(a) // error: inferred [Nothing, Minimal[Int]] does not fit the bounds of apply 
    Sugar[Int, Minimal[Int]](a) // works as expected 
} 

Il problema è che il compilatore riesce a capire il parametro interno per Minimal (Int), ma poi imposta l'altra occorrenza di T a Nothing, che ovviamente non corrisponde apply. Questi sono sicuramente gli stessi T, poiché la rimozione del primo parametro fa si che il secondo si riferisca al fatto che T non è definito.

C'è qualche ambiguità che significa che il compilatore non può dedurre il primo parametro, o si tratta di un bug? Posso aggirare questo con garbo?

Ulteriori informazioni: Questo codice è un semplice esempio di tentativo di zucchero sintattico. Il codice originale cerca di rendere |(a)| il modulo di a, dove a è un vettore. Chiaramente |(a)| è meglio che scrivere |[Float,Vector3[Float]](a)|, ma sfortunatamente non riesco a utilizzare unary_| per semplificare la procedura.

L'errore effettivo:

inferred type arguments [Nothing,Minimal[Int]] do not conform to method apply's type parameter bounds [T,X <: Sugar.S[T]]

risposta

9

Questo non è un bug del compilatore di Scala, ma è certamente una limitazione dell'inferenza di tipo di Scala. Il compilatore vuole determinare il limite su X, S[T], prima di risolverlo per X, ma il limite menziona la variabile di tipo finora non vincolata T che quindi corregge a Nothing e proviene da lì. Non rivisitare T una volta che X è stato completamente risolto ... attualmente l'inferenza di tipo procede sempre da sinistra a destra in questo tipo di caso.

Se l'esempio rappresenta accuratamente la situazione reale allora c'è una soluzione semplice,

def apply[T](x : S[T]) = x.doSomething 

Qui T verrà dedotto tale che Minimal conforme alle S[T] direttamente piuttosto che tramite una variabile di tipo limitato intermediario.

Aggiornamento

soluzione di Joshua evita anche il problema di tipo T inferire, ma in un modo completamente diverso.

def apply[T, X <% S[T]](x : X) = x.doSomething 

desugars a,

def apply[T, X](x : X)(implicit conv : X => S[T]) = x.doSomething 

Le variabili di tipo T e X possono essere risolti in modo indipendente (perché T non è menzionato in X s' legata).Ciò significa che X viene dedotto immediatamente come Minimal e che T viene risolto come parte della ricerca implicita per un valore di tipo X => S[T] per soddisfare l'argomento implicito conv. conforms in scala.Predef produce valori di questo modulo, e nel contesto garantirà che dato un argomento di tipo Minimal, T sarà dedotto come Int. Puoi vederlo come un'istanza di functional dependencies al lavoro in Scala.

+0

Sì, questa soluzione funziona nel mio caso. È anche più pulito della soluzione di Joshua (scusa Joshua!). Puoi spiegare perché la soluzione di Joshua funziona, allora? – Dylan

+0

Risposta aggiornata per fornire una spiegazione del perché la soluzione di Joshua funziona anche. –

4

C'è alcune stranezze con limiti sui tipi strutturali, provare a utilizzare una vista legato sulla S [T] invece.

def apply[T, X <% S[T]] (x: X) = x.doSomething funziona correttamente.

+1

Ottimo, funziona, ma sicuramente non ci dovrebbero essere differenze tra gli approcci? In questo caso, prendere visione di una superclasse, non è vero? Questo significa che questa correzione è solo una soluzione per un bug nel compilatore? – Dylan

+0

Ah, ora vedo, 'S' non è una superclasse - è solo una vista (in un certo senso). Quindi una vista vincolata è più appropriata, nonostante non sia richiesta nella maggior parte dei casi. – Dylan