1

Recently I was exploring kotlin for writing dsl's and I am really amazed with the dsl support in kotlin, especially with lambda with receiver pattern. Can we achieve same in scala with some implicits magic.

Is there a way I can write following dsl in scala

   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

        }

Here is the kotlins implementation for the same.

Pritam Kadam
  • 2,388
  • 8
  • 16

2 Answers2

2

Usually DSLs with control structures rely on custom monads in Scala/Haskell , smthng like (pseudo code):

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

I won't go to the details given that you're unlikely to choose this approach, but in general bind (aka flatMap) F[X], X => F[Y] allows you to simulate ";" (semicolon) traditional in programming languages and also memorize every operation on the go like it was a piece of AST (as you can get it lazily inside the bind's F[Y]) and even repeat it.

Although, unlike Haskell, Scala supports more "natural" way to stop execution of the code in the middle - namely "throw":

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 makes it even easier with breakable:

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

Basically your code works as expected with both definitions:

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

P. S. You can also build async loops using stop-exceptions approach with recover on a Future. Alternatively continuation-passing style (suspend, coroutines) could be done with monads (see CpsMonad).

dk14
  • 22,206
  • 4
  • 51
  • 88
1

Here is a short executable test.sh demonstration script which uses Breaks.scala
which uses exceptions internally. It's pretty simple.

#!/usr/bin/env scala
import scala.util.control.Breaks._
var counter1 = 0
var counter2 = 0
breakable {
  for (i <- Stream.from(1)) {

    counter1 += 1
    println(s"counter1: $counter1")
    if (counter1 == 5) break

    counter2 += 2
    println(s"counter2: $counter2")
    if (counter2 == 8) break
  }
}

Sample Output

$ ./test.sh 
counter1: 1
counter2: 2
counter1: 2
counter2: 4
counter1: 3
counter2: 6
counter1: 4
counter2: 8

Presumably loop and stopIf in your DSL could be built from breakable, break and the for expression.

jq170727
  • 13,159
  • 3
  • 46
  • 56