2011-02-04 2 views
7

Quando il compilatore Scala ha veramente bisogno delle informazioni sul tipo dei parametri delle funzioni anonime?Quando Scala ha bisogno di tipi di parametri per funzioni anonime ed espanse?

Ad esempio, data la funzione:

def callOn[T,R](target: T, f: (T => R)) = f(target) 

quindi non posso usare in questo modo:

callOn(4, _.toString) 
    => error: missing parameter type for expanded function ((x$1) => x$1.toString) 

e devo specificare

callOn(4, (_: Int).toString) 

che è piuttosto brutto. Perché il mio esempio non funziona, mentre i metodi come map, filter, foldLeft, ecc. Sulle classi di raccolta non sembrano aver bisogno di questo tipo esplicito?

risposta

14

Il trucco per inferenza dei tipi è quello di considerarla come un processo di raffinamento iterativo. Ogni blocco di parametri può essere utilizzato per dedurre alcuni dei parametri del tipo, che possono essere utilizzati nei blocchi successivi. Quindi prendere la seguente definizione:

def chain[T,A,B](x: T)(fn1: T=>A)(fn2: A=>B) = fn2(fn1(x)) 

chiamato come:

chain(2)(_*10)("xxx"+_) 

Così come questo si deduce? Innanzitutto, iniziamo con il blocco (2) che è noto per avere il tipo Int. Sostituendo che torna nel parametro T otteniamo:

def chain[A,B](x: Int)(fn1: Int=>A)(fn2: A=>B) = fn2(fn1(x)) 

Il prossimo blocco di parametri è (_*10), dove ora sappiamo il tipo di segnaposto _ essere Int ... e moltiplicando un Int da un Int dà un'altra Int . Questo è vero per il tipo restituito anche se si verifica un overflow; all'estremo estremo può generare un'eccezione, ma le eccezioni hanno il tipo Nothing che è una sottoclasse di tutto il resto nel sistema di tipi, quindi possiamo ancora dire Nothing è un Int e il tipo dedotto di Int è ancora valido.

Con A dedurre, il metodo diventa:

def chain[B](x: Int)(fn1: Int=>Int)(fn2: Int=>B) = fn2(fn1(x)) 

solo B che può essere dedotta da ("xxx"+_) Partenza.Come String + Int è un String, il metodo è ora:

def chain(x: Int)(fn1: Int=>Int)(fn2: Int=>String) = fn2(fn1(x)) 

come il tipo di ritorno del metodo direttamente dal fn2, che può anche essere indicato esplicitamente per completezza:

def chain(x: Int)(fn1: Int=>Int)(fn2: Int=>String): String = fn2(fn1(x)) 

gioco è fatto , tutti i tipi risolti in modo sicuro e il metodo ha dimostrato di essere staticamente valido.


Nel tuo caso, è necessario il tipo T da dedurre prima che sia possibile dedurre R dal tipo T=>R. Per fare questo devi dividere i parametri in due blocchi distinti, scrivendo il metodo in una forma al curry:

def callOn[T,R](target: T)(f: (T => R)) = f(target) 
6

Questa domanda è stato risposto anche qui:

Passing functions for all applicable types around

che ci si aspetta, ... per il compilatore di Scala di prendere in considerazione entrambi i parametri per due volte per dedurre il tipo corretto. Scala non lo fa, però - utilizza solo le informazioni da un elenco di parametri al successivo, ma non da un parametro all'altro. Ciò significa che i parametri f e [WS: target e f in questo caso] sono analizzati indipendentemente, senza il vantaggio di sapere cosa sia l'altro.

Nota che fa lavoro con una versione al curry:

scala> def callOn[T,R](target: T)(f: (T => R)) = f(target) 
callOn: [T,R](target: T)(f: (T) => R)R 

scala> callOn(4)(_.toString) 
res0: java.lang.String = 4 

scala> 
+0

Grazie per la risposta. Supponiamo di avere questo caso più semplice: 'def callOn4 [R] (f: (Int => R)) = f (4)'. Perché posso omettere il tipo di parametro qui, ma non se lo definisco come 'def callOn4 (f: (Int => _)) = f (4)'? –

+1

@jpp - prova solo a capire quale tipo di ritorno sarebbe dedotto nella tua seconda versione ... –