Использование Scala Ограниченные континуумы ​​для неявных Monads

Я играю с каким-то DSL, определенным монадическим интерфейсом.

Так как применение монады, использующей кучу приложений flatMap, является довольно громоздким, и я нахожу для понимания синтаксически не столь красивым, я пытаюсь неявно смешивать монадический и немонодический код с использованием разграниченных продолжений.

Это действительно работает отлично, но я действительно не доволен этими типами, потому что я должен сдерживать себя в типе "Любой", чтобы сделать на компилируемом:( Таким образом, используя "Any" и "casting" позже, результат может привести к ошибкам во время выполнения...

Вот пример кода для смешивания Option-Monad в Scala с обычным кодом, поэтому вы можете видеть, о чем я говорю:

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: continue: включить BO.scala

и тест в Scala REPL:

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

Параметр-Monad запускается с использованием функции runOption (см. функции тестирования). Функции, вызываемые внутри runOption, могут использовать функцию get или метод значение, чтобы получить значение из Option. Если значение Нет, Monad немедленно остановится и вернет Нет. Таким образом, больше нет необходимости в сопоставлении шаблонов по значению типа Option.

Проблема заключается в том, что я должен использовать тип "Any" в runOption и для типа продолжения в get.

Можно ли выразить runOption и получить с типом rank-n в scala? Поэтому я могу написать:

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

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 он всегда будет одним и тем же параметром для каждого использования value - см. test_fn, где это становится довольно ужасно. С другой стороны, вам больше не нужно указывать явный параметр типа для блока runOption, но это довольно маленькая победа в сравнении. Таким образом, теперь это полностью безопасно, но это не то, что я бы назвал удобным для пользователя, и я предполагаю, что удобство для пользователя было точкой этой библиотеки.

Я по-прежнему убежден, что типы ранга-n здесь неприменимы. Как вы можете видеть, проблема здесь в том, что теперь существует одна из типов реконструкции, а типы ранга-типа затрудняют реконструкцию, а не меньше!