2012-05-25 5 views
6

Imagínese este código:¿Por qué la función parcialmente aplicada aplaza la instanciación de clases en Scala?

class Foo { 
    println("in Foo") 

    def foo(a: Int) = a + 1 
} 

Ahora, si invocamos:

new Foo().foo _ 

instancia de la clase Foo será creado, como se esperaba:

in Foo 
res0: (Int) => Int = <function1> 

Sin embargo, si se invoca este :

new Foo().foo(_) 

el constructor de Foo no se llamará:

res1: (Int) => Int = <function1> 

Si, pues, diremos:

res1(7) 

que es cuando Foo se crea una instancia:

in Foo 
res2: Int = 8 

¿Por qué la expansión Eta frente aplicación de función parcial hacer una diferencia en la creación de instancias de clase?

+0

Esta pregunta tenía una respuesta, pero no puedo verlo más. ¿Alguien lo eliminó? –

Respuesta

2

Muchacho, eso es sutil, pero por lo que puedo decir está siguiendo completamente el Scala spec. Citaré de la versión 2.9 de la especificación.

Para su primer ejemplo: como usted dice con razón, que está viendo la expansión ETA a través de un caso especial de un Valor Método (§6.7):

The expression e _ is well-formed if e is of method type or if e is a call-by-name parameter. If e is a method with parameters, e _ represents e converted to a function type by eta expansion.

El algoritmo para la expansión eta se da en el § 6.26.5 que se puede seguir para dar la siguiente reemplazo para la expresión new Foo().x1 _:

{ 
    val x1 = new Foo(); 
    (y1: Int) => x1.(y1); 
} 

Esto implica que cuando se está utilizando la expansión eta, todos los sub-expresiones se evalúan en el punto en el c se produce la conversión (si he entendido correctamente el significado de la frase "sub-expresión máxima") y la expresión final es la creación de una función anónima.

En el segundo ejemplo, los paréntesis adicionales significa que el compilador se verá en § 6.23 (específicamente, "Marcador de posición de sintaxis para funciones anónimas) y crear una función anónima directamente.

An expression (of syntactic category Expr) may contain embedded underscore symbols _ at places where identifiers are legal. Such an expression represents an anonymous function where subsequent occurrences of underscores denote successive parameters.

En ese caso , y siguiendo el algoritmo en esa sección, su expresión termina siendo este:

(x1: Int) => new Foo().foo(x1) 

la diferencia es sutil y, como se explica muy bien por @Antoras, en realidad muestra sólo en presencia del código de efecto secundario.

Tenga en cuenta que hay una corrección de errores en curso para el caso de bloques de códigos de llamada por nombre (consulte, por ejemplo, this question, this bug y this bug).

PostScript: En ambos casos, la función anónima (x1:Int) => toto se expandió a

new scala.Function1[Int, Int] { 
    def apply(x1: Int): Int = toto 
} 
1

porque se expande a

(x: Int) => new Foo().foo(x) 

Por lo tanto, sólo se están creando esa instancia de Foo cuando se llama a esa función.

Y la razón por la primera instancia de Foo inmediato es porque se expande a

private[this] val c: (Int) => Int = { 
    <synthetic> val eta$0$1: Foo = new Foo(); 
    ((a: Int) => eta$0$1.foo(a)) 
}; 
<stable> <accessor> def c: (Int) => Int = Foo.this.c; 

Y Foo es conseguir aquí una vez instanciado se define c.

2

No estoy totalmente seguro, pero creo que la razón por la cual hay una diferencia, es que Scala no es un lenguaje de programación puramente funcional - que permite efectos secundarios:

scala> class Adder { var i = 0; def foo(a:Int)={i+=1;println(i);a+1} } 
defined class Adder 

scala> val curriedFunction = new Adder().foo _ 
curriedFunction: (Int) => Int = <function1> 

scala> val anonymousFunction = new Adder().foo(_) 
anonymousFunction: (Int) => Int = <function1>  

scala> curriedFunction(5) 
1 
res11: Int = 6 

scala> curriedFunction(5) 
2 
res12: Int = 6 

scala> anonymousFunction(5) 
1 
res13: Int = 6 

scala> anonymousFunction(5) 
1 
res14: Int = 6 

La función anónima es tratada como:

val anonymousFunction = x => new Adder().foo(x) 

mientras que la función curry se trata como:

val curriedFunction = { 
    val context = new Adder() 
    (a:Int) => context foo a 
} 

La función curried cumple la forma tradicional de las funciones curried en los lenguajes funcionales: una función curried es una función que se aplica a algunos datos y evalúa esta función parcialmente aplicada. En otras palabras: en base a algunos datos, se crea un contexto que se almacena y se puede usar más adelante. Esto es exactamente lo que está haciendo curriedFunction. Debido a que Scala permite el estado mutable, se puede cambiar el contexto, un hecho que puede conducir a un comportamiento inesperado como se ve en la pregunta.

Los lenguajes puramente funcionales como Haskell no tienen este problema porque no permiten tales efectos secundarios. En Scala uno tiene que asegurarse por sí mismo de que el contexto creado por la función curried es realmente puro.Si este no es el caso y se exige el comportamiento de funciones puramente currificadas, se deben usar funciones anónimas porque no almacenan un contexto (lo que puede ser problemático si la creación del contexto es costosa y debe hacerse a menudo).

Cuestiones relacionadas