Какие параметры типа следует использовать, чтобы List.map работал с продолжениями с разделителями?

Я в основном следую примеру, приведенному в Scala. Страница API для продолжений с разделителями. Код ниже работает нормально:

import scala.util.continuations._
import scala.collection.mutable.HashMap

val sessions = new HashMap[Int, Int=>Unit]
def ask(prompt: String): Int @cps[Unit] = shift {
  ret: (Int => Unit) => {
    val id = sessions.size
    printf("%s\nrespond with: submit(0x%x, ...)\n", prompt, id)
    sessions += id -> ret
  }
}

def submit(id: Int, addend: Int): Unit = {
  sessions.get(id) match {
    case Some(continueWith) => continueWith(addend)
  }
}

def go = reset {
  println("Welcome!")
  val first = ask("Please give me a number")
  val second = ask("Please enter another number")
  printf("The sum of your numbers is: %d\n", first + second)
}

Однако, когда я изменяю go на следующее:

def go = reset {
  println("Welcome!")
  List("First?","Second?").map[Int @cps[Unit]](ask)
}

Я получаю эту ошибку:

error: wrong number of type parameters for method map: [B, That](f: String => B)
(implicit bf: scala.collection.generic.CanBuildFrom[List[String],B,That])That
             List("First?","Second?").map[Int @cps[Unit]](ask)
                                         ^

Добавление Any в качестве параметра второго типа не помогает. Любая идея, какие типы я должен поставлять?


person Mike Stay    schedule 23.03.2013    source источник


Ответы (3)


Причина в том, что это просто невозможно без создания CPS-преобразованного метода map на List: CPS-аннотации заставляют компилятор выворачивать ваши методы «наизнанку», чтобы передать продолжение туда, где оно нужно, а стандартный List.map делает не подчиняться преобразованному договору. Если вы хотите на некоторое время окунуться в бутылки Кляйна, вы можете просмотреть файлы классов, созданные из вашего исходного кода, в частности сигнатуры методов.

Это основная причина, по которой плагин CPS никогда не станет полным решением этой проблемы, и это не из-за недостатков, а из-за врожденного несоответствия между «прямым» кодом и стилем передачи продолжения.

person Roland Kuhn    schedule 24.03.2013
comment
Итак, если я реализую свой собственный метод map, используя цикл while, а не встроенный метод, он будет работать, потому что он может преобразовать это в CPS? - person Mike Stay; 24.03.2013
comment
Плагин CPS ограничен (и, по моему опыту, не полностью свободен от ошибок), я бы посоветовал сделать его простым. - person Roland Kuhn; 24.03.2013

Вам нужно указать правильный параметр для неявного CanBuildFrom:

List("First?","Second?").map[Int @cps[Unit], List[Int @cps[Unit]](ask)

Но действительно ли вам нужно явно указывать тип? может быть, просто сделать .map(ask) будет работать.

person Alois Cochard    schedule 23.03.2013
comment
Ни одно из этих предложений не работает. Компилятор отвечает на первое предложение type arguments do not conform to method map's type parameter bounds, а на второе - no type parameters for method map exist so that it can be applied to arguments. Означает ли последнее, что это невозможно сделать? - person Mike Stay; 24.03.2013
comment
Да, действительно, боюсь, я не понимаю, почему именно. Но может оказаться возможным предоставить экземпляр CanBuildFrom, соответствующий необходимому типу. Что, если вы просто добавите implicit val cbf: CanBuildFrom[List[String], Int @cps[Unit], List[Int @cps[Unit]] = null непосредственно перед строкой, выполняющей карту? - person Alois Cochard; 24.03.2013
comment
Не удается найти тип CanBuildFrom - person Mike Stay; 24.03.2013
comment
scala-lang.org/api/current/ - person Alois Cochard; 24.03.2013
comment
Конечно, извини. Это дает ту же ошибку, что параметры типа для карты метода не существуют... - person Mike Stay; 24.03.2013

Вот самое близкое, что я мог решить. Он использует shiftR для уточнения продолжения, а не для его сброса, использует foldRight для создания цепочки приостановленного продолжения, использует блок сдвига/сброса для получения продолжения после приостановки и метод «анимации» для запуска приостановленного продолжения.

import scala.collection.mutable.HashMap
import scala.util.continuations._

val sessions = new HashMap[Int, (Unit=>Unit, Int)]
val map = new HashMap[Int, Int]

def ask(pair:(String, Int)) = pair match { 
  case (prompt, index) => shiftR { (ret: Unit => Unit) => {
    val id = sessions.size
    printf("%s\nrespond with: submit(0x%x, ...)\n", prompt, id)
    sessions += id -> (ret, index)
    ()
  }}
}

def submit(id: Int, addend: Int): Unit = {
  sessions.get(id) match {
    case Some((continue, index)) => { map.put(index, addend); continue() }
  }
}

def sum(m:HashMap[Int,Int]) : Int = {
  m.fold[(Int, Int)]((0, 0))((a, b) => (0, {a._2+b._2}))._2
}

type Suspended = ControlContext[Unit,Unit,Unit]

class AnimateList(l:List[Suspended]) {
  def suspend(k: => Unit) = (c: Unit) => k
  def animate(k:Unit => Unit): Unit = {
    l.foldRight(k)(
      (elem: Suspended, acc: Unit => Unit) => suspend(elem.fun(acc, ex => ())))()
  }
}

implicit def listToAnimateList(l:List[Suspended]) = new AnimateList(l)

reset { 
  val conts = List("First?","Second?","Third?").zipWithIndex.map(ask)
  shift { conts.animate }
  println(sum(map))
}
person Mike Stay    schedule 26.03.2013
comment
Приведенное выше решение захватывает только ту часть продолжения, которая идет после, а не до. Это решение охватывает все. - person Mike Stay; 28.03.2013