ScaLearning 5 – DSL Exploration & Operation Precedence
Like many developers who make the journey from Java to Scala, I often find myself amazed at how much easier it is to do some things, or how much easier it is to express myself in Scala.
“ScaLearning” will be a series of short blog-posts just documenting little tidbits I find interesting, confusing, amusing, or otherwise worthy of talking about.
DSL Exploration: Our Tale So Far
For those interested in the whole DSL Exploration “mini-series”, we began with DSL Exploration & Operator Notation.
To quickly summarize our tale so far, I decided to explore the creation of a DSL to expose some of Scala’s nuances. My project of choice was codifying the LOLCode language as a Scala DSL. So far we have created a syntax for outputting data to the screen.
Working syntax so far:
U SEEZ "Hello World"
Learned Lesson 2: Operation Precedence
Now that we’ve successfully defined “U SEEZ”, the LOLCode spec allows us to add a bang at the end of a sentence to “cancel” the newline. While this feels a little backwards, we can attempt it.
Our goal:
U SEEZ "Hello World" // Includes newline U SEEZ "Goodbye"! // No newline
My first plan of attack was to make “SEEZ” return an object that has the “!” method, which would do a “print” instead of a “println”. Fortunately, before attempting this solution I realized that if I were to do so there would be no method call to trigger the non-bang “println”.
Attempt two, maybe we can allow SEEZ to accept a different object. That object can have a “!” method. Here’s what I’m aiming for:
U.SEEZ( "Hello World".!() )
We’ll also need an implicit to turn Strings into this special new object. Here goes.
scala> class Printable(val text : String, val bang : Boolean) {
| override def toString() = if (bang) {text} else {text + "\n"}
| def ! = new Printable(text, !bang)
| }
defined class Printable
scala> class OutputStream {
| def SEEZ(p : Printable) = print(p.toString())
| }
defined class OutputStream
scala> val U = new OutputStream
U: OutputStream = OutputStream@e208506
scala> implicit def stringToPrintable(s : String) : Printable = new Printable(s, false)
stringToPrintable: (s: String)Printable
scala> U SEEZ "Hello World"
Hello World
That’s good news, we’ve managed to maintain the old behaviour, so let’s give our new “!” method a try:
scala> U SEEZ "Hello World"! :10: error: value ! is not a member of Unit
Foiled again! So what happened here? As “println” returns type “Unit”, which is the Scala equivalent to “void”, we can venture a guess that the “!” method was actually run on the result of “println”:
U.SEEZ("Hello World").!()
As I’m sure you’ve guessed, the issue at hand is operator precedence. Let’s experiment a little:
scala> class MyObj(val v : String) {
| def in(m : MyObj) = {println("[" + v + ".in(" + m.v + ")]"); new MyObj(v + m.v) }
| def post() = {println("[" + v + ".post()]"); this}
| def unary_+ = {println("[" + v + ".pre()]"); this}
| }
defined class MyObj
scala> implicit def stringToMyObj(s : String) : MyObj = new MyObj(s);
stringToMyObj: (s: String)MyObj
scala> +"a"
[a.pre()]
res2: MyObj = MyObj@7987b796
scala> + "a" in "b"
[a.pre()]
[a.in(b)]
res3: MyObj = MyObj@3cd41115
scala> + "a" in "b" post
[a.pre()]
[a.in(b)]
[ab.post()]
res4: MyObj = MyObj@6dcc55fb
scala> "a" in "b" in "c"
[a.in(b)]
[ab.in(c)]
res5: MyObj = MyObj@69cad977
scala> "a" in "b" post
[a.in(b)]
[ab.post()]
res6: MyObj = MyObj@37b24706
scala> "a" post
[a.post()]
res7: MyObj = MyObj@7ea4b9da
scala> "a".post in "b"
[a.post()]
[a.in(b)]
res8: MyObj = MyObj@7e28388b
scala> "a" post in "b"
:1: error: ';' expected but string literal found.
"a" post in "b"
^
We seem to be allowed to omit all the dots and brackets when we chain infix operators, but we’re unable to chain an infix operator to the result of a postfix operator unless the postfix used a dot to call. The postfix call at the end of a chain also seems to apply to the result of the previous infix call.
Note: By using the ‘dot’ in ["a".post in "b"] we are no longer using an ‘operator’. Explicit method calls are referred to as “function applications” in the reference
According to the Scala Reference:
- If there are several infix operations, operators with higher precedence bind more closely than operators with lower precedence
- If there are consecutive infix operations with operators of the same precedence, all operators must have the same associativity
- Postfix operators always have lower precedence than infix operators
While prefix operators aren’t mentioned, they appear to have a higher precedence than infix operators via experimentation:
scala> class MyObj(val v : String) {
| def +(m : MyObj) = {println("[" + v + ".in(" + m.v + ")]"); new MyObj(v + m.v) }
| def unary_+ = {println("[" + v + ".pre()]"); this}
| }
defined class MyObj
scala> implicit def stringToMyObj(s : String) : MyObj = new MyObj(s);
stringToMyObj: (s: String)MyObj
scala> +"a" + +"b"
[a.pre()]
[b.pre()]
[a.in(b)]
res21: MyObj = MyObj@144f1ada
The end result of this discussion is another compromise. After experimenting with various notations, I gave up and returned to my original plan of a “!” method on an object returned by “SEEZ”:
scala> class Printable(output:String) {
| def ! = { print(output); }
| def !! = { println(output); }
| }
defined class Printable
scala> class OutputStream {
| def SEEZ(output: String) : Printable = new Printable(output)
| }
defined class Printable
scala> val U = new OutputStream
U: OutputStream = OutputStream@608d116e
scala> U SEEZ "Hello World"!
Hello World
scala> U SEEZ "Hello World"!!
Hello World
scala>
Stay tuned, next time we’ll attempt to put the pieces together and run HelloLol.scala