Scala foreach comportamiento extraño

Quiero iterar sobre una lista de valores usando una hermosa línea en Scala.

Por ejemplo, este funciona bien:

scala> val x = List(1,2,3,4) x: List[Int] = List(1, 2, 3, 4) scala> x foreach println 1 2 3 4 

Pero si uso el marcador de posición _ , me da un error:

 scala> x foreach println(_ + 1) :6: error: missing parameter type for expanded function ((x$1) =>x$1.$plus(1)) x foreach println(_ + 1) ^ 

¿Porqué es eso? ¿No se puede inferir el tipo de comstackdor aquí?

Esta:

 x foreach println(_ + 1) 

es equivalente a esto:

 x.foreach(println(x$1 => x$1 + 1)) 

No hay ninguna indicación sobre cuál podría ser el tipo de x$1 y, para ser honesto, no tiene ningún sentido imprimir una función.

Obviamente (para mí) significa imprimir x$0 + 1 , donde x$0 sería el parámetro pasado por foreach , en su lugar. Pero, consideremos esto … foreach toma, como parámetro, una Function1[T, Unit] , donde T es el parámetro tipo de la lista. Lo que está pasando a foreach es println(_ + 1) , que es una expresión que devuelve Unit .

Si escribieras, en cambio x foreach println , estarías pasando algo completamente diferente. println la función (*) println , que toma Any y devuelve la Unit , ajustando, por lo tanto, los requisitos de foreach .

Esto se confunde un poco debido a las reglas de expansión de _ . Se expande al delimitador de expresión más interno (paréntesis o llaves), excepto si están en lugar de un parámetro, en cuyo caso significa algo diferente: aplicación de función parcial.

Para explicar esto mejor, mira estos ejemplos:

 def f(a: Int, b: Int, c: Int) = a + b + c val g: Int => Int = f(_, 2, 3) // Partial function application g(1) 

Aquí, aplicamos los argumentos segundo y tercero a f , y devolvimos una función que requiere solo el argumento restante. Tenga en cuenta que solo funcionaba así porque indiqué el tipo de g , de lo contrario tendría que indicar el tipo de argumento que no estaba aplicando. Continuemos:

 val h: Int => Int = _ + 1 // Anonymous function, expands to (x$1: Int => x$1 + 1) val i: Int => Int = (_ + 1) // Same thing, because the parenthesis are dropped here val j: Int => Int = 1 + (_ + 1) // doesn't work, because it expands to 1 + (x$1 => x$1 + 1), so it misses the type of `x$1` val k: Int => Int = 1 + ((_: Int) + 1) // doesn't work, because it expands to 1 + (x$1: Int => x$1 + 1), so you are adding a function to an `Int`, but this operation doesn't exist 

Analicemos k con más detalle, porque este es un punto muy importante. Recuerde que g es una función Int => Int , ¿verdad? Entonces, si tuviera que escribir 1 + g , ¿tendría algún sentido? Eso es lo que se hizo en k .

Lo que confunde a las personas es que lo que realmente querían era:

 val j: Int => Int = x$1 => 1 + (x$1 + 1) 

En otras palabras, quieren que x$1 reemplazando _ salte fuera del paréntesis, y al lugar correcto. El problema aquí es que, si bien puede parecerles obvio cuál es el lugar adecuado, no es obvio para el comstackdor. Considere este ejemplo, por ejemplo:

 def findKeywords(keywords: List[String], sentence: List[String]) = sentence.filter(keywords contains _.map(_.toLowerCase)) 

Ahora, si tuviéramos que expandir esto fuera del paréntesis, obtendríamos esto:

 def findKeywords(keywords: List[String], sentence: List[String]) = (x$1, x$2) => sentence.filter(keywords contains x$1.map(x$2.toLowerCase)) 

Lo cual definitivamente no es lo que queremos. De hecho, si _ no flatMap delimitado por el delimitador de expresión más interno, nunca se podría usar _ con el map nested, flatMap , filter y foreach .

Ahora, de vuelta a la confusión entre la función anónima y la aplicación parcial, mira aquí:

 List(1,2,3,4) foreach println(_) // doesn't work List(1,2,3,4) foreach (println(_)) // works List(1,2,3,4) foreach (println(_ + 1)) // doesn't work 

La primera línea no funciona debido a cómo funciona la notación de operación. Scala solo ve que println devuelve Unit , que no es lo que foreach espera.

La segunda línea funciona porque el paréntesis le permite a Scala evaluar println(_) como un todo. Es una aplicación de función parcial, por lo que devuelve Any => Unit , lo cual es aceptable.

La tercera línea no funciona porque _ + 1 es una función anónima, que está pasando como parámetro para println . No está haciendo que println parte de una función anónima, que es lo que quería.

Finalmente, lo que pocas personas esperan:

 List(1,2,3,4) foreach (Console println _ + 1) 

Esto funciona. Por qué lo hace se deja como un ejercicio para el lector. 🙂

(*) En realidad, println es un método. Cuando escribe x foreach println , no está pasando un método, porque los métodos no se pueden pasar. En cambio, Scala crea un cierre y lo pasa. Se expande así:

 x.foreach(new Function1[Any,Unit] { def apply(x$1: Any): Unit = Console.println(x$1) }) 

El guión bajo es un poco complicado. De acuerdo con la especificación, la frase:

 _ + 1 

es equivalente a

 x => x + 1 

Molesto

 x foreach println (y => y + 1) 

rendimientos:

 :6: error: missing parameter type x foreach println (y => y + 1) 

Si agrega algunos tipos en:

 x foreach( println((y:Int) => y + 1)) :6: error: type mismatch; found : Unit required: (Int) => Unit x foreach( println((y:Int) => y + 1)) 

El problema es que está println una función anónima a println y no puede manejarla. Lo que realmente quiere hacer (si está tratando de imprimir el sucesor de cada elemento en la lista) es:

 x map (_+1) foreach println 
 scala> for(x <- List(1,2,3,4)) println(x + 1) 2 3 4 5 

Existe una limitación extraña en Scala para la profundidad de anidamiento de las expresiones con guión bajo. Está bien visto en el siguiente ejemplo:

  scala> List(1) map(1+_) res3: List[Int] = List(2) scala> Some(1) map (1+(1+_)) :5: error: missing parameter type for expanded function ((x$1) => 1.+(x$1)) Some(1) map (1+(1+_)) ^ 

Parece un error para mí.

 Welcome to Scala version 2.8.0.Beta1-prerelease (Java HotSpot(TM) Client VM, Java 1.6.0_17). Type in expressions to have them evaluated. Type :help for more information. scala> val l1 = List(1, 2, 3) l1: List[Int] = List(1, 2, 3) scala> scala> l1.foreach(println(_)) 1 2 3