Продолжения и для понимания в чем несовместимость?

Я новичок в Scala и пытаюсь осмыслить продолжения, я пытаюсь воспроизвести оператор yield return C #.

После этого сообщения я написал следующий код:

package com.company.scalatest

import scala.util.continuations._;

object GenTest {

  val gen = new Generator[Int] {
    def produce = {
      yieldValue(1)
      yieldValue(2)
      yieldValue(3)
      yieldValue(42)
    }
  }
  // Does not compile :(

  //  val gen2 = new Generator[Int] {
  //    def produce = {
  //      var ints = List(1, 2, 3, 42);
  //
  //      ints.foreach((theInt) => yieldValue(theInt));
  //    }
  //  }

  // But this works?
  val gen3 = new Generator[Int] {
    def produce = {
      var ints = List(1, 2, 3, 42);
      var i = 0;
      while (i < ints.length) {
        yieldValue(ints(i));
        i = i + 1;
      }
    }
  }

  def main(args: Array[String]): Unit = {
    gen.foreach(println);
    //    gen2.foreach(println);
    gen3.foreach(println);
  }
}

abstract class Generator[E] {

  var loopFn: (E => Unit) = null

  def produce(): Unit @cps[Unit]

  def foreach(f: => (E => Unit)): Unit = {
    loopFn = f
    reset[Unit, Unit](produce)
  }

  def yieldValue(value: E) =
    shift { genK: (Unit => Unit) =>
      loopFn(value)
      genK(())
      ()
    }
}

Как видите, gen2 закомментирован, поскольку он не компилируется. Поскольку я могу легко перебирать содержимое списка с помощью цикла while (см. gen3), я ожидал, что цикл foreach будет работать так же хорошо.

Ошибка компиляции следующая:

no type parameters for method foreach: (f: Int => B)Unit exist so that 
it can be applied to arguments (Int => Unit @scala.util.continuations.cpsParam[Unit,Unit])  
 --- because --- 
argument expression's type is not compatible with formal parameter type;  
found   : Int => Unit @scala.util.continuations.cpsParam[Unit,Unit]  
required: Int => ?B 

Почему я получаю эту ошибку и есть ли способ ее обойти, используя что-нибудь более чистое, чем цикл while?

Спасибо


person GuiSim    schedule 19.01.2012    source источник
comment
возможный дубликат Реализация yield (доходность) с использованием продолжений Scala   -  person Daniel C. Sobral    schedule 20.01.2012
comment
Я не ищу ответа, в котором рассказывается, как воспроизвести «yield return», используя продолжения Scala. Я ищу причину, по которой gen2 в моем примере не работает. «Доходность» - это просто контекст, в котором я столкнулся с этой проблемой.   -  person GuiSim    schedule 20.01.2012
comment
Я сменил тему на что-то более подходящее для вашего вопроса.   -  person Daniel C. Sobral    schedule 20.01.2012
comment
Ответ на вопрос, почему gen2 не работает, в основном связан с тем, как плагин компилятора продолжения переводит сдвиги и сбросы в базовые экземпляры ControlContext. Я соберу правильный ответ сегодня позже, а пока вот анонс, который я написал на эту тему: earldouglas.com/monadic-continuations-in-scala   -  person earldouglas    schedule 20.01.2012


Ответы (1)


Сначала давайте посмотрим, что потребуется для компиляции gen2.

object CpsConversions {

  import scala.collection.IterableLike
  import scala.util.continuations._

  implicit def cpsIterable[A, Repr](xs: IterableLike[A, Repr]) = new {
    def cps = new {
      def foreach[B](f: A => Any@cpsParam[Unit, Unit]): Unit@cpsParam[Unit, Unit] = {
        val it = xs.iterator
        while(it.hasNext) f(it.next)
      }
    }
  }
}

object GenTest {

  import CpsConversions.cpsIterable
  val gen2 = new Generator[Int] {
    def produce = {
      var ints = List(1, 2, 3, 42)
      ints.cps.foreach((theInt) => yieldValue(theInt))
    }
  }

Теперь посмотрим, что происходит. Исходный gen2 не может быть скомпилирован в следующей строке:

ints.foreach((theInt) => yieldValue(theInt))

Поскольку тип yieldValue включает аннотацию @cpsParam, плагин продолжения преобразует функцию, переданную методу foreach, в один из типов:

Int => Unit @cpsParam[Unit,Unit]

Вверху по иерархии List[Int] вы увидите foreach, определенный как:

foreach [U] (f: (Int) ⇒ U): Unit

Это проблема, поскольку типы не совпадают, и Scala не знает, как перейти от Int => U к Int => Unit @cpsParam[Unit,Unit]. Чтобы исправить это, я добавил CPS-версию foreach в неявное преобразование, к которому вы можете получить доступ, вызвав cps на любом IterableLike.

Было бы очень хорошо, если бы это неявное преобразование могло быть выполнено без явного вызова cps, но я не нашел способа заставить компилятор Scala распознавать применимость такого неявного преобразования, чтобы добавить новый foreach в ваш список. Возможно, это связано с порядком, в котором компилятор использует плагин продолжения, но я слишком мало знаю об этом процессе, чтобы быть уверенным.

Так что это все хорошо для foreach. В вашем вопросе упоминается для понимания, которое потребует определения любого из filter, map или flatMap (в зависимости от того, что происходит в вашем понимании). Я реализовал их в ссылке в моем вышеупомянутом комментарии, который расширяет объект CpsConversions выше, чтобы обеспечить общее понимание.

person earldouglas    schedule 23.01.2012