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

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

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

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

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

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

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

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

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

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

Ответы

Ответ 1

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

Ответ 2

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

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

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

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

Схема перевода следующая. На первом шаге каждый генератор p <- e, где p не является неопровержимым (§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, где., (возможно, пустая) последовательность генераторов или охранников, переведена to:
    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([email protected] <- e) yield { val [email protected] = e0; (x, x0) }
    

Ответ 3

В этом случае, это действительно немного магии компилятора. Перевод из for-assrehension в формат filter/map/flatmap - это особый бит desugaring, подобно преобразованию специальных форм обновления и методов применения.