首页 > 解决方案 > 自定义循环 dsl 终止于循环体中条件匹配的点

问题描述

最近我正在探索kotlin编写 dsl,我对 kotlin 中的 dsl 支持感到非常惊讶,尤其是带有接收器模式的lambda 。我们可以通过一些隐含的魔法在 scala 中实现同样的效果吗?

有没有办法我可以写下面的 dslscala

   var counter1 = 0
    var counter2 = 0
    loop {
            counter1 += 1
            println(counter1)
            stopIf(counter1 == 5) // loop should terminate here and not execute rest of the code if condition matches

            counter2 += 2
            println(counter2)
            stopIf(counter2 == 8) // loop should terminate here and not execute rest of the code if condition matches

        }

是相同的 kotlins 实现。

标签: scalaimplicit

解决方案


通常具有控制结构的 DSL 依赖于 Scala/Haskell 中的自定义 monad,类似于(伪代码):

//I'm going to skip monad transformers and Free assuming it's "all in one monad" (state, AST, interpreters)
for {
  counter1 <- variable(0)
  counter2 <- variable(0)
  repetition <- loop //should be trampolined to avoid stack-overflow
  _ <- counter1.update(_ + 1)
  counter1value <- counter1.get
  _ = print(counter1value)
  _ <- repetition.stopIf(counter1Value == 5)
  _ <- counter2.update(_ + 2)
  counter2value <- counter2.get
  _ = print(counter2value)
  _ <- repetition.stopIf(counter2Value == 8) 
  _ <- repetition.repeatForever
}

鉴于您不太可能选择这种方法,我不会详细说明,但总的来说bind(又名 flatMap)F[X], X => F[Y]允许您模拟“;” (分号)在编程语言中是传统的,并且在旅途中记住每一个操作,就像它是一块 AST(因为你可以在 bind's 中懒惰地得到它F[Y]),甚至重复它。

尽管与 Haskell 不同,Scala 支持更“自然”的方式来停止中间代码的执行——即“抛出”:

import scala.util._

object StopException extends Throwable

def loop (block: => Unit) = while ({
  Try(block) match {
    case Failure(StopException) => false
    case Failure(t) => throw t
    case Success(_) => true
  }
}) {}

def stopIf(condition: Boolean) = if (condition) throw StopException

Scala 使用可破坏性使其变得更加容易:

import util.control.Breaks._
def loop (block: => Unit) = breakable(while(true){block})
def stopIf(condition: Boolean) = if (condition) break

基本上,您的代码在两个定义中都按预期工作:

var counter1 = 0
var counter2 = 0
loop {
   counter1 += 1
   println(counter1)
   stopIf(counter1 == 5)
   counter2 += 2
   println(counter2)
   stopIf(counter2 == 8)
}

1
2
2
4
3
6
4
8

PS 您还可以使用停止异常方法和 Future 上的恢复来构建异步循环。或者,可以使用 monad 来完成延续传递风格(挂起、协程)(参见 CpsMonad)。


推荐阅读