Реализация yield (yield return) с использованием продолжений Scala

Как можно реализовать C # yield return, используя продолжения Scala? Я хотел бы иметь возможность писать Scala Iterators в том же стиле. В комментариях к этому сообщению новостей Scala есть удар, но он не работает (пробовал использовать бета-версию Scala 2.8.0). Ответы на связанный с этим вопрос предполагают, что это возможно, но хотя Я какое-то время играл с разделенными продолжениями, я не могу точно понять, как это сделать.


person Yang    schedule 04.02.2010    source источник
comment
Что не работает в этом примере? Он не компилируется или не дает ожидаемых результатов? Есть упоминание, что для того, чтобы это работало, может потребоваться CPS-совместимый foreach, но, в любом случае, было бы полезно знать, в чем проблема.   -  person Daniel C. Sobral    schedule 04.02.2010
comment
Вы можете проверить ответ Майлза Сабина на аналогичный у меня вопрос stackoverflow.com/questions/2137619/. Не уверен, что это приближает вас.   -  person huynhjl    schedule 05.02.2010
comment
На этот вопрос есть как минимум два других ответа: stackoverflow.com/questions/1655738/ stackoverflow.com/questions/2137619/ Есть также ответ на вопрос, как сделать его совместимым с операторами for: stackoverflow.com/questions/8934226/   -  person Urban Vagabond    schedule 26.07.2012


Ответы (2)


Прежде чем мы представим продолжения, нам нужно создать некоторую инфраструктуру. Ниже представлен батут, который действует на Iteration объекты. . Итерация - это вычисление, которое может либо Yield новое значение, либо Done.

sealed trait Iteration[+R]
case class Yield[+R](result: R, next: () => Iteration[R]) extends Iteration[R]
case object Done extends Iteration[Nothing]

def trampoline[R](body: => Iteration[R]): Iterator[R] = {
  def loop(thunk: () => Iteration[R]): Stream[R] = {
    thunk.apply match {
      case Yield(result, next) => Stream.cons(result, loop(next))
      case Done => Stream.empty
    }
  }
  loop(() => body).iterator
}

Батут использует внутренний цикл, который превращает последовательность из Iteration объектов в Stream. Затем мы получаем Iterator, вызывая iterator для результирующего объекта потока. Используя Stream, наша оценка будет ленивой; мы не оцениваем нашу следующую итерацию, пока она не понадобится.

Батут можно использовать для создания итератора напрямую.

val itr1 = trampoline {
  Yield(1, () => Yield(2, () => Yield(3, () => Done)))
}

for (i <- itr1) { println(i) }

Это довольно ужасно писать, поэтому давайте будем использовать продолжения с разделителями для автоматического создания наших Iteration объектов.

Мы используем операторы shift и reset, чтобы разбить вычисление на Iteration, а затем использовать trampoline, чтобы превратить Iteration в Iterator.

import scala.continuations._
import scala.continuations.ControlContext.{shift,reset}

def iterator[R](body: => Unit @cps[Iteration[R],Iteration[R]]): Iterator[R] =
  trampoline {
    reset[Iteration[R],Iteration[R]] { body ; Done }
  }

def yld[R](result: R): Unit @cps[Iteration[R],Iteration[R]] =
  shift((k: Unit => Iteration[R]) => Yield(result, () => k(())))

Теперь мы можем переписать наш пример.

val itr2 = iterator[Int] {
  yld(1)
  yld(2)
  yld(3)
}

for (i <- itr2) { println(i) }

Намного лучше!

А вот пример со страницы справочной страницы C # для yield, на которой расширенное использование. К типам может быть сложно привыкнуть, но все работает.

def power(number: Int, exponent: Int): Iterator[Int] = iterator[Int] {
  def loop(result: Int, counter: Int): Unit @cps[Iteration[Int],Iteration[Int]] = {
    if (counter < exponent) {
      yld(result)
      loop(result * number, counter + 1)
    }
  }
  loop(number, 0)
}

for (i <- power(2, 8)) { println(i) }
person Rich Dougherty    schedule 06.02.2010
comment
Я хотел бы увидеть вывод scalac -print для итератора, yld и присвоение itr2. Может ли кто-нибудь с плагином добавить это к ответу? - person retronym; 07.02.2010
comment
Я просто пытался применить это, поэтому у меня был работающий и удобный код. См. Выходные данные gist.github.com/297230 (прокрутите вниз). - person huynhjl; 07.02.2010
comment
Я бы переименовал iterator в yldIterator или что-то в этом роде, чтобы избежать путаницы. :-) - person Daniel C. Sobral; 08.02.2010
comment
Я только что увидел, что это было адаптировано в grizzled scala библиотека. - person Daniel C. Sobral; 02.09.2012
comment
Grizzled удалил генераторы в 1.1.6, поскольку он полагался на неподдерживаемый и неподдерживаемый плагин продолжения Scala. читайте здесь: github.com/bmc/grizzled-scala/blob/master /CHANGELOG.md - person Robert Fey; 09.01.2016

Мне удалось найти способ сделать это после еще нескольких часов игры. Я думал, что это было проще понять, чем все другие решения, которые я видел до сих пор, хотя впоследствии я очень высоко оценил Rich и решения Майлза.

def loopWhile(cond: =>Boolean)(body: =>(Unit @suspendable)): Unit @suspendable = {
  if (cond) {
    body
    loopWhile(cond)(body)
  }
}

  class Gen {
    var prodCont: Unit => Unit = { x: Unit => prod }
    var nextVal = 0
    def yld(i: Int) = shift { k: (Unit => Unit) => nextVal = i; prodCont = k }
    def next = { prodCont(); nextVal }
    def prod = {
      reset {
        // following is generator logic; can be refactored out generically
        var i = 0
        i += 1
        yld(i)
        i += 1
        yld(i)
        // scala continuations plugin can't handle while loops, so need own construct
        loopWhile (true) {
          i += 1
          yld(i)
        }
      }
    }
  }
  val it = new Gen
  println(it.next)
  println(it.next)
  println(it.next)
person Yang    schedule 07.02.2010
comment
Продолжение Scala не может обрабатывать циклы while? Ой! - person Daniel C. Sobral; 08.02.2010
comment
Верно. :( Надеюсь, что работа над ним еще не завершена, но я считаю, что for-computing определенно несовместимы со сдвигом, так как это означало бы разорвать карту / foreach / и т. Д. - person Yang; 28.06.2010
comment
Не больше. Вызов кода cps из цикла while возможен уже некоторое время. Формальные понимания по-прежнему не поддерживаются (я не думаю, что они когда-либо получат поддержку) - person Przemek Pokrywka; 24.06.2011
comment
@PrzemekPokrywka: ... если Scala не получит собственную виртуальную машину, не будет выпущена Java 12 или что-то в этом роде. - person Erik Kaplun; 17.02.2014