【问题标题】:Using Scala's Delimited Continuations for implicit Monads为隐式 Monad 使用 Scala 的定界延续
【发布时间】:2011-04-07 01:57:02
【问题描述】:

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

由于使用一堆 flatMap 应用程序来应用 monad 有点麻烦,而且我发现语法上的理解不是那么漂亮,所以我试图使用定界延续来隐式地混合 monadic 和非 monadic 代码。

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

这里是一些将 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

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

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

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

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)
}

谢谢!

【问题讨论】:

    标签: scala monads continuations


    【解决方案1】:

    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 块中,它始终是 same 类型参数每一次使用value--请参阅test_fn 以了解这变得非常可怕的地方。另一方面,您不再需要为 runOption 块提供显式类型参数,但相比之下这是一个很小的胜利。所以这现在是完全类型安全的,但这不是我所说的用户友好,我猜用户友好是这个库的重点。

    我仍然坚信 rank-n 类型在这里不适用。如您所见,这里的问题现在是类型重构之一,而 rank-n 类型使重构更加困难,而不是更少!

    【讨论】:

    • 谢谢。不幸的是,这不是我想要的。例如,当使用 get 方法时,就像你定义的那样,monadic 类型的返回类型也会受到限制。
    • 我不明白它们怎么可能是其他类型,就像您编写代码的方式一样。该解决方案消除了您抱怨的强制转换的需要,并且您的所有测试都运行良好。那么你能提供一个它不满足的测试用例吗?
    • 问题是到目前为止,必须 a) 提前知道完整计算的类型并像任何地方一样表示它或 b) 将返回类型设置为指定类型(就像你在get 方法)或 c)广泛使用强制转换。一个简单的例子:runOption[Boolean] { val x = Some(1); val y = 一些(2); x.value == y.value }
    • 嗯,我明白了。你应该把它作为你的测试用例之一 :) 我会考虑这个......
    • 但是您要添加的通用量词会呈现无人居住的类型。例如,forall C. (forall A . => A @cpsParam[Option[A], Option[C]]) => Option[C] 是无人居住的,因为没有人可以提供 A 参数。这就像试图写这个定义:def forall[A]:A = ...
    猜你喜欢
    • 2012-02-09
    • 2011-08-28
    • 2011-10-14
    • 2014-01-22
    • 1970-01-01
    • 1970-01-01
    • 2015-03-14
    • 1970-01-01
    • 2020-04-06
    相关资源
    最近更新 更多