Какими правилами следует руководствоваться подчеркиванием для определения анонимной функции?
Я использую _
как заполнитель для создания анонимной функции, и проблема в том, что я не могу предсказать, как Scala собирается преобразовать мой код. Точнее, он по ошибке определяет, насколько "большую" анонимную функцию я хочу.
List(1,2,3) foreach println(_:Int) //error !
List(1,2,3) foreach (println(_:Int)) //work
List(1,2,3) foreach(println(_:Int)) //work
С помощью -Xprint:typer
Я вижу, что Scala превращает первый в "большую анонимную функцию":
x$1 => List(1,2,3) foreach(println(x$1:Int))
сработавшие 2-е 3-е - это правильное превращение в то, что я хочу.
... foreach (x$1 => println(x$1:Int))
Почему это? Какое правило?
2 ответа
Простые правила для определения области подчеркивания:
- Если подчеркивание является аргументом метода, тогда область действия будет вне этого метода, в противном случае соответствующие правилам ниже;
- Если подчеркивание находится внутри выражения, ограниченного () или {}, будет использован самый внутренний такой разделитель, который содержит подчеркивание;
- При прочих равных условиях будет использовано максимально возможное выражение.
Итак, по правилу № 1 вместо println((x: Int) => x)
, сфера будет размещена снаружи (в том числе) println
,
По правилу № 2 последние два примера будут иметь функцию, разделенную скобками, поэтому (x => println(x: Int))
,
По правилу № 3, первым примером будет целое выражение, так как нет разделительных скобок.
Я считаю, что ответ мистера Собрала неверен. Действительные правила можно найти в справочнике по языку Scala, раздел 6.23, подзаголовок "Синтаксис заполнителя для анонимных функций".
Единственное правило состоит в том, что самое внутреннее выражение, которое правильно содержит подчеркивание, определяет область действия анонимной функции. Это означает, что первые два правила мистера Собрала верны, потому что вызов метода является выражением, а выражение в скобках не меняет его значения. Но третье правило противоположно истине: при прочих равных условиях будет использовано наименьшее выражение, которое имеет смысл.
К сожалению, мое объяснение поведения, которое наблюдал г-н Ласковский для своего первого примера, немного сложное и умозрительное. когда
List(1,2,3) foreach println(_:Int)
набирается в цикле чтения-оценки-печати Scala. Сообщение об ошибке:
error: type mismatch;
found : Unit
required: Int => ?
List(1,2,3) foreach println(_:Int)
^
Если вы измените пример чуть-чуть:
List(1,2,3).foreach println(_:Int)
сообщение об ошибке легче понять -
error: missing arguments for method foreach in class List;
follow this method with `_' if you want to treat it as a partially applied function
List(1,2,3).foreach println(_:Int)
^
Чтобы понять вещи немного лучше, звоните scala
таким образом: scala -Xprint:parser
, который после того, как пользователь вводит каждое выражение, вызывает печать выражения, выделенного синтаксическим анализатором. (Наряду с большим количеством мусора, который я опущу.) Для первого примера Laskowski выражение, понятное парсеру:
((x$1: Int) => List(1, 2, 3).foreach(println((x$1: Int))))
Для второго примера версия парсера
((x$1: Int) => List(1, 2, 3).foreach.println((x$1: Int)))
По всей видимости, правило контекста применяется до того, как структура выражения полностью раскрыта. В обоих случаях синтаксический анализатор догадывается, что наименьшее выражение начинается с List, хотя после вставки скобок это уже не так. Во втором примере в дополнение к этому предположению предполагается, что, поскольку println
это идентификатор, foreach println
является цепочкой методов, первый из которых не имеет аргументов. Ошибка при foreach
затем пойман до ошибки в println
, маскируя это. Ошибка при println
является то, что его результат является единицей, и foreach
требует функции. Как только вы видите дерево разбора, легко увидеть, что это правильно, но мне неясно, почему дерево разбора такое.