This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Extending the curly-braces parameter list syntax

2 replies
David Flemström
Joined: 2009-08-10,
User offline. Last seen 42 years 45 weeks ago.
Hello all,
I have a proposition to make (again).
Context
As you know, it is possible to do this:
def section[A](name: String)(body: => A): A = {
  log.info("Entering " + name)
body }
...followed by:
val result = section("Integer arithmetic") {
  val x = 2 + 5
  x + 3
}
This feature can be extremely useful for DSLs, since you get syntax that makes it look like "section {}" was something built into the language.
IdeaI think that this feature can be extended with minimal effort and almost no added complexity, but with extremely useful results. What I want to propose is as follows:
When a method or function has a parameter list that contains one single parameter, and that parameter is marked as being a "vararg" (with or without being marked as call-by-name), it should be possible to invoke said method with curly braces at the location of said parameter list, and each expression within the curly braces should compute one parameter that will be passed to the method.
ExampleGiven the following:
trait Base {
  def show: String
}

case class Foo(x: String) extends Base {
  def show = x(1)
}

case class Bar(x: String) extends Base {
  def show = x + x
}

def method(xs: Base*) = xs map (_.show) foreach println
...it should be possible to do the following:
method {
  Foo("Hi")
  Bar("Bye")
  //Illegal, because Unit `is not` a Base and can not `be treated as` a Base:
  //println("Hi")

  //Legal, because it's a definition; the value won't be sent as a parameter, though:
  val msg = "Hello again"

  Bar(msg)
  Bar(msg)
}
//prints:
//>i
//>ByeBye
//>Hello againHello again
//>Hello againHello again

Use casesTake, for example, the following very short but very generic DSL definition (From a quick Scala REPL session):
trait DSLImpl {
  type Res
  type Expr
  def bind(exprs: Seq[Expr]): Res
  def eval(body: Expr*): Res = bind(body.toSeq)
}

sealed trait JSExpr {
  def show: String
}

object JavaScript extends DSLImpl {
  type Res = String
  type Expr = JSExpr
  def bind(exprs: Seq[Expr]) = exprs.map(_.show).mkString("", ";\n", ";")
}

case class JSString(value: String) extends JSExpr {
  def show = "'" + value.replace("'", "\\'") + "'" //simplified
}

case class JSInt(value: Int) extends JSExpr {
  def show = value.toString
}

object js {
  def unapply(str: String) = Some(new JSString(str))
  def unapply(n: Int) = Some(new JSInt(n))
}

case class alert(msg: JSExpr) extends JSExpr {
  def show = "alert(" + msg.show + ")"
}

//... other JSExprs

val javascript = JavaScript.eval {
  val js(hello) = "Hello World"
  alert(hello)
  alert(hello)
}
println(javascript)

//Prints:
//>alert('Hello World');
//>alert('Hello World');
The above demonstrates a completely type-safe DSL that is intuitive to use. And imagine what would happen if there was a JSFunc class with a JSExpr* constructor! (Exercise for the reader to implement it)
Alternative methodsSome might argue that (today) the above could have been written as:
val js(hello) = "Hello World"

JavaScript.eval(
  alert(hello),
  alert(hello)
)
There are several disadvantages with this, however:
  • The DSL does not look integrated at all (no curly braces).
  • There is a need for commas.
  • There is no benefit of a scope that can be used for local variables etc.
What are your thoughts on this? Something for a compiler plugin, or useful enough for the main compiler to have?
Cheers,David Flemström
Naftoli Gugenheim
Joined: 2008-12-17,
User offline. Last seen 42 years 45 weeks ago.
Re: Extending the curly-braces parameter list syntax
It's very useful if you need to pass items that you haven't defined. But if you are writing the child node definitions, take a look at Rodant's SWT DSL on http://github.com/rodant ; you can implement such a DSL today using the same pattern. Disclaimer: I only skimmed the post so it may be more complicated.

On Thu, Apr 22, 2010 at 2:49 PM, David Flemström <david [dot] flemstrom [at] gmail [dot] com> wrote:
Hello all,
I have a proposition to make (again).
Context
As you know, it is possible to do this:
def section[A](name: String)(body: => A): A = {
  log.info("Entering " + name)
body }
...followed by:
val result = section("Integer arithmetic") {
  val x = 2 + 5
  x + 3
}
This feature can be extremely useful for DSLs, since you get syntax that makes it look like "section {}" was something built into the language.
IdeaI think that this feature can be extended with minimal effort and almost no added complexity, but with extremely useful results. What I want to propose is as follows:
When a method or function has a parameter list that contains one single parameter, and that parameter is marked as being a "vararg" (with or without being marked as call-by-name), it should be possible to invoke said method with curly braces at the location of said parameter list, and each expression within the curly braces should compute one parameter that will be passed to the method.
ExampleGiven the following:
trait Base {
  def show: String
}

case class Foo(x: String) extends Base {
  def show = x(1)
}

case class Bar(x: String) extends Base {
  def show = x + x
}

def method(xs: Base*) = xs map (_.show) foreach println
...it should be possible to do the following:
method {
  Foo("Hi")
  Bar("Bye")
  //Illegal, because Unit `is not` a Base and can not `be treated as` a Base:
  //println("Hi")

  //Legal, because it's a definition; the value won't be sent as a parameter, though:
  val msg = "Hello again"

  Bar(msg)
  Bar(msg)
}
//prints:
//>i
//>ByeBye
//>Hello againHello again
//>Hello againHello again

Use casesTake, for example, the following very short but very generic DSL definition (From a quick Scala REPL session):
trait DSLImpl {
  type Res
  type Expr
  def bind(exprs: Seq[Expr]): Res
  def eval(body: Expr*): Res = bind(body.toSeq)
}

sealed trait JSExpr {
  def show: String
}

object JavaScript extends DSLImpl {
  type Res = String
  type Expr = JSExpr
  def bind(exprs: Seq[Expr]) = exprs.map(_.show).mkString("", ";\n", ";")
}

case class JSString(value: String) extends JSExpr {
  def show = "'" + value.replace("'", "\\'") + "'" //simplified
}

case class JSInt(value: Int) extends JSExpr {
  def show = value.toString
}

object js {
  def unapply(str: String) = Some(new JSString(str))
  def unapply(n: Int) = Some(new JSInt(n))
}

case class alert(msg: JSExpr) extends JSExpr {
  def show = "alert(" + msg.show + ")"
}

//... other JSExprs

val javascript = JavaScript.eval {
  val js(hello) = "Hello World"
  alert(hello)
  alert(hello)
}
println(javascript)

//Prints:
//>alert('Hello World');
//>alert('Hello World');
The above demonstrates a completely type-safe DSL that is intuitive to use. And imagine what would happen if there was a JSFunc class with a JSExpr* constructor! (Exercise for the reader to implement it)
Alternative methodsSome might argue that (today) the above could have been written as:
val js(hello) = "Hello World"

JavaScript.eval(
  alert(hello),
  alert(hello)
)
There are several disadvantages with this, however:
  • The DSL does not look integrated at all (no curly braces).
  • There is a need for commas.
  • There is no benefit of a scope that can be used for local variables etc.
What are your thoughts on this? Something for a compiler plugin, or useful enough for the main compiler to have?
Cheers,David Flemström

David Flemström
Joined: 2009-08-10,
User offline. Last seen 42 years 45 weeks ago.
Re: Extending the curly-braces parameter list syntax
On Thu, Apr 22, 2010 at 9:25 PM, Naftoli Gugenheim <naftoligug [at] gmail [dot] com> wrote:
It's very useful if you need to pass items that you haven't defined. But if you are writing the child node definitions, take a look at Rodant's SWT DSL on http://github.com/rodant ; you can implement such a DSL today using the same pattern. Disclaimer: I only skimmed the post so it may be more complicated.
 SSWT uses a solution that works, but there are drawbacks:
  1. It is not possible to selectively evaluate part of a block or to treat it differently depending on a partial evaluation of it. You have to execute it in one go.
  2. Lazy evaluation is impossible (except for the whole block).
  3. You need one of:
    • Some kind of session sentinel variable (he uses a "currentParent" variable which also stores state itself) that you mutate to store the operations you want to perform, which means that you have yet another thing to synchronize and keep track of in the world of mutability.
    • A DynamicVariable/ThreadLocal variable that serves the same purpose, but additionally lowers performance.
  4. Both the consumer (In my example above, it was JavaScript.eval) and all of the "DSL methods" you want to use need to have access to this variable. This means that you'll have to provide mechanisms to expose it, by using private[package] and similar workarounds (That is what they are, imho).
  5. You can't add custom nodes externally, including child nodes (as you said), container nodes, etc.
  6. You can't even add custom consumers (e.g. JavaScript.evalCompressed or JSTools.toICodeAST etc.) externally
  7. Unit tests? Forget it, unless you do some more "private[package] protected[outer.package]" magic to make the sentinel variable available to your test cases (or dependency inject it)
  8. You need to set up a completely new system for every type you need this functionality for. SSWT only uses the Composite type for everything, but what if I want to insert a nested tree of Ints as a child node somewhere? Then I can't use the currentParent variable any more; I will have to invent a new sentinel that stores Ints/accumulates Int operations.
  9. All of this adds up to a lot of boilerplate if someone doesn't manage to stuff it into a generic library
There are of course advantages as well:
  1. It is possible to include expressions with unrelated types in your blocks without worry. You can just write 'println("reached me")' instead of 'val _ = println("reached me")' as would be required with my syntax. This bears a lot of similarity to the pros/cons of the for-comprehension construct.
  2. It could be more efficient since no sequence/container of anything needs to be created.
Let me know if you find something else to add.
Cheers,David Flemström

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland