views:

502

answers:

1

Hello,

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!

+1  A: 

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!

pelotom
thanks. Unfortunately this is not what I'm looking for. For example when using the get method, like you've defined it, the return type of the monadic type will be restricted, too.
urso
I don't understand how they could be any other type, the way you've written your code. This solution obviates the need for casting, which you were complaining about, and all your tests run fine. So can you provide a testcase that it doesn't satisfy?
pelotom
The problem is as of now one has to a) know the type of the complete computation in advance and denote it like everywhere or b) set the return type to a specified type (like you did in your get method) or c) make extensive use of casting. A simple example:runOption[Boolean] { val x = Some(1); val y = Some(2); x.value == y.value }
urso
Hm, I see. You should've made that one of your testcases :) I'll think about this...
pelotom
But the universal quantifiers you want to add would render uninhabited types. For instance, `forall C. (forall A . => A @cpsParam[Option[A], Option[C]]) => Option[C]` is uninhabited, because nobody could ever provide the `A` argument. It's like trying to write this definition: `def forall[A]:A = ...`
pelotom
Or I guess it's more accurate to say that it's trivially inhabited, because from an absurd argument you can derive any result.
pelotom
haha, yeh... the absurd argument could be that the first call to get tells about the A. But it is not the A which bothers me, it is pretty fine for A to be of type Any. It should not be really noticable in the continuation context. But I don't really know about the transformations done by the continuations plugin. What really bothers me is the type C, since the return type of "runOption" depends on the continuation function itself in "get". But In Scala we need to tell the continuation's type in advance, which is unfortunately not known "yet"...
urso
The `shift` block in your case only has 2 distinct types, `A` and `Option[B]`. In general it could have 3, but because the result of your `shift` block matches the result of your `reset` block, the last 2 are the same.
pelotom
Take a look at this: http://jim-mcbeath.blogspot.com/2010/08/delimited-continuations.html, it explains comprehensively how the transformation is done.
pelotom
thanks for the link. Will read it tomo. Your second attempt was actually my first one... I guess you've found out why I did not like that approach much :)
urso