Scala function body definition help wanted

Viewed 68

I'm trying making a scala dsl with this syntax:

val a = Agent() setup { agent => // agent reference.
  agent add "Hello World!"
}
a add "Not allowed" // Atm this is allowed.

where I can call method add outside the setup function body and to call add method I have to write agent => at the beginning of setup function body.

What I've done atm is a trait Agent:

trait Agent {

  def setup(f: Agent => Unit): Agent

  def add(s: String): Agent
}

case class AgentImpl(strings: Seq[String]) extends Agent {
  override def setup(f: Agent => Unit): Agent = {
    f(this)
    this
  }

  override def add(s: String): Agent = copy(strings = strings:+s)
}

object Agent {
  def apply(): Agent = AgentImpl(Seq.empty)
}

What I have to do is this:

val a = Agent() setup { // No more references to agent
    add "Hello World!"
  }
  a add "Not allowed" // This mustn't be allowed. Compilation error.

where I haven't a reference to agent with agent => at the start of setup function body and if I try to do an add outside the setup function body I get an error.

Is this possible in Scala?

I can change every part of the code, add any number of traits/classes/objects and other things but not change the syntax of my DSL.

1 Answers

You could do something like this:

sealed trait Agent {
  def getFoos: List[String]
  def getBars: List[Int]
}

object Agent {
  import scala.collection.mutable.ListBuffer
  
  private final class Builder {
    private[this] val foos: ListBuffer[String] = ListBuffer.empty
    private[this] val bars: ListBuffer[Int] = ListBuffer.empty
    
    private def addFoo(foo: String): Unit = {
      foos += foo
    }
    
    private def addBar(bar: Int): Unit = {
      bars += bar
    }
    
    private[Agent] def result(): Agent = new Agent {
      override final val getFoos: List[String] =
        foos.result()
      
      override final val getBars: List[Int] =
        bars.result()
    }
  }
  
  private[Agent] type BuilderStep = Builder => Unit
  
  object Builder {
    private[Agent] def create() = new Builder()
    
    object Steps {
      def addFoo(foo: String): BuilderStep =
        _.addFoo(foo)
      
      def addBar(bar: Int): BuilderStep =
        _.addBar(bar)
    }
  }
  
  def setup(steps: BuilderStep*): Agent = {
    val builder = Builder.create()
    steps.foreach(s => s(builder))
    builder.result()
  }
}

Which can be used like this:

import Agent.Builder.Steps._

val agent = Agent.setup(
  addFoo("A"),
  addFoo("B"),
  addBar(1),
  addBar(2),
  addFoo("C"),
  addBar(3),
  addFoo("D")
)

But maybe a traditional immutable builder would be equally good and simpler.


Code running here.

Related