在scala continuation中,如何以CPS形式编写循环?

在scala continuation中,如何以CPS形式编写循环?,scala,continuations,continuation-passing,Scala,Continuations,Continuation Passing,我试图在以下位置实现一个示例: 使用scala延拓: object ReverseGrad_CPSImproved { import scala.util.continuations._ case class Num( x: Double, var d: Double = 0.0 ) { def +(that: Num) = shift { (cont: Num => Unit) => val y = Num(x + t

我试图在以下位置实现一个示例:

使用scala延拓:

object ReverseGrad_CPSImproved {

  import scala.util.continuations._

  case class Num(
      x: Double,
      var d: Double = 0.0
  ) {

    def +(that: Num) = shift { (cont: Num => Unit) =>
      val y = Num(x + that.x)

      cont(y)

      this.d += y.d
      that.d += y.d
    }

    def *(that: Num) = shift { (cont: Num => Unit) =>
      val y = Num(x * that.x)

      cont(y)

      this.d += that.x * y.d
      that.d += this.x * y.d
    }
  }

  object Num {

    implicit def fromX(x: Double): Num = Num(x)
  }

  def grad(f: Num => Num @cps[Unit])(x: Double): Double = {

    val _x = Num(x)
    reset { f(_x).d = 1.0 }

    _x.d
  }
}
只要我使用的是简单表达式,它就可以工作:

  it("simple") {

    val fn = { x: Num =>
      val result = (x + 3) * (x + 4)

      result
    }

    val gg = grad(fn)(3)

    println(gg)
  }
但一旦我开始使用loop,它就会崩溃:


  it("benchmark") {

    import scala.util.continuations._

    for (i <- 1 to 20) {

      val n = Math.pow(2, i).toInt

      val fn = { x: Num =>
        var result = x + 1

        for (j <- 2 to n) {
          result = result * (x + j)
        }

        result
      }

      val nanoFrom = System.nanoTime()
      val gg = grad(fn)(3)
      val nanoTo = System.nanoTime()

      println(s"diff = $gg,\t time = ${nanoTo - nanoFrom}")
    }
  }


[Error] /home/peng/git-spike/scalaspike/meta/src/test/scala/com/tribbloids/spike/meta/multistage/lms/ReverseGrad_CPSImproved.scala:78: found cps expression in non-cps position
one error found

it(“基准”){
导入scala.util.continuations_
为了
var结果=x+1

对于(j在CPS中,您必须重写代码,这样您就不会在同一上下文中执行嵌套/迭代/递归调用,而是只执行一步计算并向前传递部分结果

例如,如果您想计算数字a到B的乘积,可以通过以下方式实现:

import scala.util.continuations._

case class Num(toDouble: Double) {

  def get = shift { cont: (Num => Num) =>
    cont(this)
  } 

  def +(num: Num) = reset {
    val a  = num.get
    Num(toDouble + a.toDouble)
  }

  def *(num: Num) = reset {
    val a  = num.get
    Num(toDouble * a.toDouble)
  }
}

// type annotation required because of recursive call
def product(from: Int, to: Int): Num @cps[Num] = reset { 
  if (from > to) Num(1.toDouble)
  else Num(from.toDouble) * product(from + 1, to)
}

def run: Num = reset {
  product(2, 10)
}

println(run)
(见此)

最有趣的是这个片段:

reset {
  if (from > to) Num(1.toDouble)
  else Num(from.toDouble) * product(from + 1, to)
}
在这里,编译器(插件)将其重写为类似于:

input: (Num => Num) => {
  if (from > to) Num(1.toDouble)
  else {
    Num(from.toDouble) * product(from + 1, to) // this is virtually (Num => Num) => Num function!
  } (input)
}
编译器可以执行此操作,因为:

  • 它观察
    shift
    reset
    调用的内容
    • 这两种方法都创建了一个接受某个参数
      A
      的东西,并返回中间结果
      B
      (例如在这个或另一个
      reset
      )和最终结果
      C
      (运行合成的最终结果时得到的结果)(表示为
      A@cpsParam[B,C]
      -如果
      B=:=C
      可以使用类型别名
      a@cps[a]
    • reset
      在处理获取参数(
      A@cpsParam[B,C]
      中的
      A
      )并将其传递给所有嵌套的CPS调用并获得中间结果时,更容易不疯狂地传递参数(因此
      A@cpsParam[B,C]
      中的
      B
      )并使整个块返回最终结果-
      C
      A@cpsParam[B,C]
    • shift
      将功能
      (A=>B=>C
      提升到
      A@cpsParam[B,C]
  • 当它看到返回类型为
    Input@cpsParam[Output1,Output2]
    时,它知道应该重写代码以引入一个参数并将其传递到那里
实际上,它的底层要复杂得多,但基本上就是这样

与此同时,你要做你的工作

        for (j <- 2 to n) {
          result = result * (x + j)
        }

for(j在CPS中,您必须重写代码,这样您就不会在同一上下文中执行嵌套/迭代/递归调用,而是只执行一步计算并向前传递部分结果

例如,如果您想计算数字a到B的乘积,可以通过以下方式实现:

import scala.util.continuations._

case class Num(toDouble: Double) {

  def get = shift { cont: (Num => Num) =>
    cont(this)
  } 

  def +(num: Num) = reset {
    val a  = num.get
    Num(toDouble + a.toDouble)
  }

  def *(num: Num) = reset {
    val a  = num.get
    Num(toDouble * a.toDouble)
  }
}

// type annotation required because of recursive call
def product(from: Int, to: Int): Num @cps[Num] = reset { 
  if (from > to) Num(1.toDouble)
  else Num(from.toDouble) * product(from + 1, to)
}

def run: Num = reset {
  product(2, 10)
}

println(run)
(见此)

最有趣的是这个片段:

reset {
  if (from > to) Num(1.toDouble)
  else Num(from.toDouble) * product(from + 1, to)
}
在这里,编译器(插件)将其重写为类似于:

input: (Num => Num) => {
  if (from > to) Num(1.toDouble)
  else {
    Num(from.toDouble) * product(from + 1, to) // this is virtually (Num => Num) => Num function!
  } (input)
}
编译器可以执行此操作,因为:

  • 它观察
    shift
    reset
    调用的内容
    • 这两种方法都创建了一个接受某个参数
      A
      的东西,并返回中间结果
      B
      (例如在这个或另一个
      reset
      )和最终结果
      C
      (运行合成的最终结果时得到的结果)(表示为
      A@cpsParam[B,C]
      -如果
      B=:=C
      可以使用类型别名
      a@cps[a]
    • reset
      在处理获取参数(
      A@cpsParam[B,C]
      中的
      A
      )并将其传递给所有嵌套的CPS调用并获得中间结果时,更容易不疯狂地传递参数(因此
      A@cpsParam[B,C]
      中的
      B
      )并使整个块返回最终结果-
      C
      A@cpsParam[B,C]
    • shift
      将功能
      (A=>B=>C
      提升到
      A@cpsParam[B,C]
  • 当它看到返回类型为
    Input@cpsParam[Output1,Output2]
    时,它知道应该重写代码以引入一个参数并将其传递到那里
实际上,它的底层要复杂得多,但基本上就是这样

与此同时,你要做你的工作

        for (j <- 2 to n) {
          result = result * (x + j)
        }

for(j如果你改变了状态(
vard:Double=0.0
this.d+=that.x*y.d
)?如果你改变了状态(
vard:Double=0.0
this.d+=that.x*y.d
),你为什么要使用CPS?