views:

322

answers:

1

I am not a Groovy expert, but I did read the book "Groovy in Action". In Groovy, each closure comes with a "context", where the items inside the closure can get access to pseudo-variables like "this", "owner", and "delegate", that let the items know who called the closure. This allows one to write DSLs like this (from Groovy in Action):

swing = new SwingBuilder()
frame = swing.frame(title:'Demo') {
  menuBar {
    menu('File') {
      menuItem 'New'
      menuItem 'Open'
    }
  }
  panel {
    // ...
  }
}

Note that 'menuBar' "knows" that it belongs to 'frame' because it can get context information about the owner and delegate of the closure.

Is this possible to do in Scala? If so, how?

Thanks, Ralph

+9  A: 

One way is to use a scala.util.DynamicVariable to track the context. Something like the SwingBuilder could be implemented as

import scala.util.DynamicVariable
import javax.swing._

object SwingBuilder {
  case class Context(frame: Option[JFrame], parent: Option[JComponent])
}

class SwingBuilder {
  import SwingBuilder._
  val context = new DynamicVariable[Context](Context(None,None))

  def frame(title: String)(f: =>Unit) = {
    val res = new JFrame(title)
    res.add(new JPanel())
    context.withValue(Context(Some(res),context.value.parent)){f;res}
  }

  def menuBar(f: =>Unit) = {
    val mb = new JMenuBar()
    context.value.frame.foreach(_.setJMenuBar(mb))
    context.withValue(Context(context.value.frame,Some(mb))){f;mb}
  }

  def menu(title: String)(f: =>Unit) = {
    val m = new JMenu(title)
    context.value.parent.foreach(_.asInstanceOf[JMenuBar].add(m))
    context.withValue(Context(context.value.frame,Some(m))){f;m}
  }

  def menuItem(title: String) = {
    val mi = new JMenuItem(title)
    context.value.parent.foreach(_.asInstanceOf[JMenu].add(mi))
  }
}

object Test {
  def main(args: Array[String]) {
    val builder = new SwingBuilder()
    import builder._

    val f = frame("Demo") {
      val mb = menuBar {
        menu("File") {
          menuItem("New")
          menuItem("Open")
        }
      }
    }
    f.setVisible(true)
  }
}
Geoff Reedy
Scala never ceases to amaze me :)... Thanks.
Ralph