Скала foreach странное поведение
Я хочу перебрать список значений, используя красивую однострочную строку в 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
Но если я использую заполнитель _
, это дает мне ошибку:
scala> x foreach println(_ + 1)
<console>:6: error: missing parameter type for expanded function ((x$1) =>x$1.$plus(1))
x foreach println(_ + 1)
^
Это почему? Компилятор не может определить тип здесь?
5 ответов
Это:
x foreach println(_ + 1)
эквивалентно этому:
x.foreach(println(x$1 => x$1 + 1))
Там нет никаких указаний относительно того, что может быть тип x$1
и, честно говоря, бессмысленно печатать функцию.
Вы, очевидно (для меня) хотели напечатать x$0 + 1
, где x$0
будет параметр передан foreach
вместо. Но давайте рассмотрим это... foreach
принимает в качестве параметра 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
Первая строка не работает из-за того, как работает нотация операций. Скала просто видит это 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) })
Подчеркивание немного сложнее. Согласно спецификации, фраза:
_ + 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
В 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+_))
^
Похоже, ошибка для меня.
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