Ответ 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) })