Scala странное поведение

Я хочу перебрать список значений с помощью красивого однострочного в Scala.

Например, это хорошо работает:

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

scala> x foreach println
1
2
3
4

Но если я использую placeholder _, это дает мне ошибку:

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

Почему? Не можете ли вы здесь написать компилятор?

Ответы

Ответ 1

Это:

x foreach println(_ + 1)

эквивалентно этому:

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

Нет никаких указаний относительно того, что может быть типом x$1, и, честно говоря, не имеет смысла печатать функцию.

Вы, очевидно, (для меня) предназначались для печати x$0 + 1, где x$0 вместо этого был передан параметр foreach. Но рассмотрим это... foreach принимает в качестве параметра a Function1[T, Unit], где T является параметром типа списка. То, что вы переходите в foreach, вместо этого, это println(_ + 1), которое является выражением, которое возвращает Unit.

Если вы написали, вместо x foreach println, вы передадите совершенно другую вещь. Вы передадите функцию (*) println, которая берет Any и возвращает Unit, устанавливая, следовательно, требования foreach.

Это немного путает из-за правил разложения _. Он расширяется до самого внутреннего разделителя выражений (скобки или фигурные скобки), за исключением случаев, когда они находятся вместо параметра, и в этом случае это означает другое: приложение частичной функции.

Чтобы лучше объяснить это, посмотрите на следующие примеры:

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

Здесь мы применяем второй и третий аргументы к f и возвращаем функцию, требующую только оставшегося аргумента. Обратите внимание, что он работал только потому, что я указал тип g, иначе мне пришлось бы указать тип аргумента, который я не применял. Продолжить:

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

Давайте обсудим k более подробно, потому что это очень важный момент. Напомним, что g является функцией Int => Int, правильно? Итак, если бы я набрал 1 + g, это имело бы смысл? То, что было сделано в k.

Что смущает людей, так это то, что они действительно хотели:

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

Другими словами, они хотят, чтобы x$1 заменил _, чтобы перейти за пределы скобок и в нужное место. Проблема здесь в том, что, хотя для них может показаться очевидным, что такое надлежащее место, это не очевидно для компилятора. Рассмотрим этот пример, например:

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

Теперь, если бы мы расширили это за пределами скобки, мы получим следующее:

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

Это определенно не то, что мы хотим. На самом деле, если _ не ограничивается самым внутренним разделителем выражений, никогда нельзя использовать _ с вложенными map, flatMap, filter и foreach.

Теперь, вернемся к путанице между анонимной функцией и частичным приложением, посмотрите здесь:

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

Первая строка не работает из-за того, как работает нотация записи. Scala просто видит, что println возвращает Unit, чего не ожидает foreach.

Вторая строка работает, потому что скобка позволяет Scala оценивать println(_) в целом. Это приложение частичной функции, поэтому оно возвращает Any => Unit, что приемлемо.

Третья строка не работает, потому что _ + 1 - анонимная функция, которую вы передаете как параметр println. Вы не делаете часть анонимной функции println, которая вам нужна.

Наконец, мало кто ожидает:

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

Это работает. Почему это делается, как упражнение для читателя.: -)

(*) На самом деле, println является методом. Когда вы пишете x foreach println, вы не передаете метод, потому что методы не могут быть переданы. Вместо этого Scala создает закрытие и передает его. Он расширяется следующим образом:

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

Ответ 2

Подчеркивание немного сложнее. Согласно спецификации, фраза:

_ + 1

эквивалентно

x => x + 1

Попытка

x foreach println (y => y + 1)

дает:

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

Если вы добавляете некоторые типы в:

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

Проблема в том, что вы передаете анонимную функцию println, и она не справляется с ней. То, что вы действительно хотите сделать (если вы пытаетесь напечатать преемника для каждого элемента в списке):

x map (_+1) foreach println

Ответ 3

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

Ответ 4

В Scala существует странное ограничение для глубины вложенности выражений с подчеркиванием. Это хорошо видно на следующем примере:

 scala> List(1) map(1+_)
 res3: List[Int] = List(2)

 scala> Some(1) map (1+(1+_))
 <console>:5: error: missing parameter type for expanded function ((x$1) => 1.+(x$1))
        Some(1) map (1+(1+_))
                     ^

Похож на ошибку для меня.

Ответ 5

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