Скала "<-" для понимания

Я обнаружил, что у Скалы всегда есть "естественное объяснение" чему угодно. Всегда что-то вроде "ооо, но это просто функция, вызываемая для этого и того объекта с этим и этим параметром". В некотором смысле, ничто не является действительно волшебством компилятора, поскольку мы знаем это от других языков.

Мой вопрос об операторе <-, который используется в следующем коде:

for(i <- 0 to 10) println(i)

В этом примере я вижу переписывание чего-то вроде:

0.to(10).foreach((i:Int)=>println(i))

но это не объясняет, как я попал в анонимную функцию внутри функции foreach. В точке, где вы пишете i, это не объект и еще не объявленная переменная. Так что же это такое и как оно переносится внутрь foreach?

Я думаю, что я наконец-то обнаружил нечто, что на самом деле является магией компилятора

Спасибо за ваше время.

Чтобы уточнить, у меня вопрос: как работает оператор <- в 1-й строке кода, поскольку я не являюсь объектом, для которого он может быть вызван как функция.

3 ответа

Решение

<- является определяемым языком символом ключевого слова, как => но в отличие от -> (который является определенным символом). Поскольку он является частью базовой грамматики Scala, его можно использовать для создания привязок (для i в вашем примере), что не может быть сделано с помощью пользовательских конструкций.

Чтобы дополнить ответ Дейва, вот схема перевода "для понимания" из спецификации языка Scala:

Понимание for (enums) yield e оценивает выражение e для каждой привязки, сгенерированной перечислителями. Последовательность перечислителя всегда начинается с генератора; за этим могут следовать другие генераторы, определения значений или защита.

Генератор p <- e производит привязки из выражения e который каким-то образом сопоставляется с шаблоном p, Определение значения val p = e связывает имя значения p (или несколько имен в шаблоне p) к результату оценки выражения e, Охранник if e содержит логическое выражение, которое ограничивает перечисляемые привязки.

Точное значение генераторов и охранников определяется переводом в вызовы четырех методов: map, filter, flatMap, а также foreach, Эти методы могут быть реализованы по-разному для разных типов несущих.

Схема перевода выглядит следующим образом. На первом этапе каждый генератор p <- eгде р не является неопровержимым (§8.1) для типа e заменяется

 p <- e.filter { case p => true; case _ => false }

Затем следующие правила применяются неоднократно, пока все понимания не будут устранены.

  • Для понимания for (p <- e) yield e0 переводится на e.map { case p => e0 },

  • Для понимания for (p <- e) e0 переводится на e.foreach { case p => e0 },

  • Для понимания for (p <- e; p0 <- e0 . . .) yield e00, где.,, является (возможно, пустой) последовательностью генераторов или охранников, переводится в:
    e.flatMap { case p => for (p0 <- e0 . . .) yield e00 },

  • Для понимания for (p <- e; p0 <- e0 . . .) e00 где.,, является (возможно, пустой) последовательностью генераторов или охранников, переводится в:
    e.foreach { case p => for (p0 <- e0 . . .) e00 },

  • Генератор p <- e в сопровождении охранника if g переводится на один генератор:
    p <- e.filter((x1, . . . , xn) => g )
    где x1.,,, xn свободные переменные p,

  • Генератор p <- e с последующим определением значения val p0 = e0 переводится в следующий генератор пар значений, где x а также x0 свежие имена:

    val (p, p0) <- 
      for(x@p <- e) yield { val x0@p0 = e0; (x, x0) }
    

В этом случае это действительно немного волшебства компилятора. Перевод из-за понимания в форму фильтра / карты / плоской карты - это особая часть десагеринга, очень похожая на преобразование специальных форм обновления и применения методов.

Другие вопросы по тегам