2011-02-04 5 views
7

¿Cuándo realmente necesita el compilador Scala el tipo de información de los parámetros de las funciones anónimas?¿Cuándo necesita Scala tipos de parámetros para funciones anónimas y expandidas?

Por ejemplo, dada esta función:

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

entonces no se puede utilizar de esta manera:

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

y tengo que especificar

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

que es bastante feo. ¿Por qué no funciona mi ejemplo, mientras que métodos como map, filter, foldLeft, etc. en las clases de colección no parecen necesitar este tipo explícito?

Respuesta

14

El truco para la inferencia de tipos es considerarla como un proceso de refinamiento iterativo. Cada bloque de parámetros se puede usar para inferir algunos de los parámetros de tipo, que luego se pueden usar en bloques posteriores. Así que toma la siguiente definición:

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

llamado como:

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

Entonces, ¿cómo es esto inferido? Primero, comenzamos con el bloque (2) que se sabe que tiene el tipo Int. Sustituyendo de nuevo en que el parámetro T obtenemos:

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

El siguiente bloque de parámetros es (_*10), donde ahora sabemos el tipo del marcador de posición _ ser Int ... y multiplicando una Int por un Int da otra Int . Esto es cierto para el tipo devuelto incluso si ocurre un desbordamiento; en el extremo puede lanzar una excepción, pero las excepciones tienen el tipo Nothing que es una subclase de todo lo demás en el sistema de tipo, por lo que todavía podemos decir Nothing es-un Int y el tipo inferido de Int sigue siendo válido.

Con A inferido, el método se convierte en:

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

única B que se puede inferir de ("xxx"+_) Dejando.Como String + Int es una String, el método es ahora:

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

Como el tipo de retorno del método viene directamente de fn2, que también se pueden mostrar de forma explícita para la integridad:

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

Ahí lo tienes , todos los tipos se resuelven de forma segura, y el método demostrado ser estáticamente válido.


En su caso, es necesario el tipo T debe inferirse antes de que sea posible inferir R del tipo T=>R. Para hacer esto, debe dividir los parámetros en dos bloques distintos, escribiendo el método en currículum:

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

Esta pregunta también ha sido respondida aquí:

Passing functions for all applicable types around

que espera, ... por el compilador de Scala para tomar en cuenta ambos parámetros a dos veces para inferir los tipos correctos. Scala no hace eso, sin embargo, solo usa información de una lista de parámetros a la siguiente, pero no de un parámetro al siguiente. Eso significa que los parámetros f y a [WS: target y f en este caso] se analizan de forma independiente, sin tener la ventaja de saber lo que es el otro.

Nota que hace trabajo con una versión 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

Gracias por su respuesta. Supongamos que tengo este caso más simple: 'def callOn4 [R] (f: (Int => R)) = f (4)'. ¿Por qué puedo omitir el tipo de parámetro aquí, pero no si lo defino como 'def callOn4 (f: (Int => _)) = f (4)'? –

+1

@jpp - solo intenta averiguar qué tipo de devolución se deduciría en tu segunda versión ... –

Cuestiones relacionadas