使用Scala的隐式Monad的分隔连续

时间:2010-09-01 23:55:38

标签: scala monads continuations

我正在使用由monadic接口定义的某种DSL。

由于使用一堆flatMap应用程序应用monad有点麻烦,我发现语法上的理解并不那么美,我试图使用分隔的延续来隐含地混合monadic和non monadic代码。

它实际上工作正常,但我真的对这些类型感到不满意,因为我必须将自己限制在“任意”类型中以便编译:(。因此在以后使用“Any”和“cast”时结果需要可能导致运行时错误...

以下是将Scala中的Option-Monad与常规代码混合的示例代码,以便您可以看到我在说什么:

object BO {

  import scala.util.continuations._

  def runOption[C](ctx: => Any @cpsParam[Option[Any],Option[Any]]): Option[C] = {
    val tmp : Option[Any] = reset {
      val x : Any = ctx
      Some(x)
    }
    tmp.asInstanceOf[Option[C]]
  }

  def get[A](value:Option[A]) = shift { k:(A=>Option[Any]) => 
    value.flatMap(k)
  }     

  class CPSOption[A](o:Option[A]) {
    def value = get[A](o)
  }

  implicit def opt2cpsopt[A](o:Option[A]) = new CPSOption(o)

  def test1 = runOption[Int] {
    val x = get(None)
    x
  }

  def test2 = runOption[Int] {
    val x = Some(1).value
    x
  }

  def test3 = runOption[Int] {
    val x = Some(1)
    val y = Some(2)
    x.value + y.value
  }            

  def test_fn(x:Option[Int], y:Option[Int], z:Option[Int]) = runOption[Int] {
    x.value * x.value + y.value * y.value + z.value * z.value
  }            

  def test4 = test_fn(Some(1), Some(2), Some(3))

  def test5 = test_fn(Some(1), None, Some(3))
}

使用以下代码编译代码:     $ scalac -P:continuations:enable BO.scala

并在scala REPL中进行测试:

scala> import BO._
scala> test4
res0: Option[Int] = Some(14)
scala> test5
res1: Option[Int] = None

使用 runOption 功能运行Option-Monad(请参阅测试功能)。在 runOption 中调用的函数可以使用 get 函数或方法从选项中获取值。如果值为,Monad将立即停止并返回。因此,不再需要对选项类型的值进行模式匹配。

问题是,我必须在 runOption 中使用“Any”类型,并在 get 中使用延续类型。

是否可以在scala中使用rank-n类型表达 runOption 获取? 所以我可以写:

def runOption[C](ctx: forall A . => A @cpsParam[Option[A], Option[C]]) : Option[C] = 
  ...

def get[A](value:Option[A]) = shift { k:(forall B . A=>Option[B]) => 
  value.flatMap(k)
}

谢谢!

1 个答案:

答案 0 :(得分:5)

Scala没有更高级别的多态性,尽管您可以通过一些扭曲来模拟它(请参阅herehere)。好消息是,这里不需要那种火力。试试这些:

def runOption[A](ctx: => A @cps[Option[A]]): Option[A] = reset(Some(ctx))

def get[A](value:Option[A]) = shift { k:(A=>Option[A]) => value flatMap k }

第二次尝试

好的,让我们再试一次,根据您在runOption块中使用多个类型的示例:

object BO {

  import scala.util.continuations._

  def runOption[A](ctx: => A @cps[Option[A]]): Option[A] = reset(Some(ctx))

  def get[A, B](value:Option[A]):A @cps[Option[B]] = shift { k:(A=>Option[B]) => 
    value flatMap k
  }

  class CPSOption[A](o:Option[A]) {
    def value[B] = get[A, B](o)
  }

  implicit def opt2cpsopt[A](o:Option[A]) = new CPSOption[A](o)

  def test1 = runOption {
    val x = get[Int, Int](None)
    x
  }

  def test2 = runOption {
    Some(1).value[Int]
  }

  def test3 = runOption {
    val x = Some(1)
    val y = Some(2)
    x.value[Int] + y.value[Int]
  }

  def test_fn(x:Option[Int], y:Option[Int], z:Option[Int]) = 
    runOption (x.value[Int] * x.value[Int] + 
               y.value[Int] * y.value[Int] + 
               z.value[Int] * z.value[Int])

  def test4 = test_fn(Some(1), Some(2), Some(3))

  def test5 = test_fn(Some(1), None, Some(3))

  def test6 = runOption { val x = Some(1)
                          val y = Some(2)
                          x.value[Boolean] == y.value[Boolean] }
}

不幸的是,正如您所看到的,结果不是很好。由于Scala的类型推理能力有限,您需要为value的大多数用途提供显式类型参数,并且在任何给定的runOption块中,它始终是相同的 value的每个用法的类型参数 - 请参阅test_fn,了解其中非常糟糕的地方。另一方面,您不再需要为runOption块提供显式类型参数,但相比之下,这是一个非常小的胜利。所以现在这是完全类型安全的,但它不是我称之为用户友好的,我猜测用户友好性是这个库的重点。

我仍然相信rank-n类型在这里不适用。正如你所看到的,这里的问题现在是类型重建之一,而rank-n类型使重建更多变得困难,而不是更少!