使用 Scala 的 Delimited Continuations 实现隐式 Monad

发布于 2024-09-17 15:47:15 字数 2209 浏览 7 评论 0原文

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

由于使用一堆 flatMap 应用程序来应用 monad 有点麻烦,而且我发现理解语法在语法上并不那么漂亮,所以我尝试使用分隔延续来隐式混合单子和非单子代码。

它实际上工作得很好,但我真的对这些类型不满意,因为我必须将自己限制在可编译时使用“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中用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)
}

谢谢!

I'm playing with some kind of DSL defined by an monadic interface.

Since applying the monad using a bunch of flatMap applications is kind of cumbersome and I find for-comprehension syntactically not that beautiful, I'm trying to implicitely mix monadic and non monadic code using delimited continuations.

It's actually working fine, but I'm really not happy with the types, because I have to restrain my self to the type "Any" to make at compilable :(. Thus using "Any" and "casting" later when the result is needed may lead to runtime errors...

Here is some example Code for mixing the Option-Monad in Scala with regular code, so you can see what I am talking about:

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

compile the code with:
$ scalac -P:continuations:enable BO.scala

and test in scala REPL:

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

The Option-Monad is run using the runOption function (see test functions). Functions being called inside runOption can use the get function or the value method to get the value from an Option. In case the value is None, the Monad will stop immediately and return None. So there is no more need for pattern matching on value of type Option.

The Problem is, that I have to use the type "Any" in runOption and for the type of the continuation in get.

Is it possible to express runOption and get with rank-n types in scala?
So I can write:

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

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

感性 2024-09-24 15:47:59

Scala 没有更高阶的多态性,尽管您可以通过一些扭曲来模拟它(请参阅 此处

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

我仍然坚信,n 级类型在这里不适用。正如您所看到的,这里的问题现在是类型重构之一,而 n 阶类型使重构变得更加困难,而不是更少!

Scala doesn't have higher-rank polymorphism, although you can simulate it with some contortions (see here and here). The good news is, that kind of firepower isn't necessary here. Try these:

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 }

Second Attempt

Ok, let's try this again, in light of your example using more than one type in the runOption block:

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

Unfortunately, as you can see, the results ain't pretty. Due to Scala's limited type inferencing ability, you need to provide an explicit type parameter for most uses of value, and in any given runOption block, it'll always be the same type parameter for every usage of value--see test_fn for where this gets pretty horrible. On the other hand, you no longer need to provide an explicit type parameter to the runOption block, but that's a pretty small win in comparison. So this is completely type-safe now, but it's not what I'd call user-friendly, and I'm guessing user-friendliness was the point of this library.

I remain convinced that rank-n types are not applicable here. As you can see, the problem here is now one of type reconstruction, and rank-n types make reconstruction more difficult, not less!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文