Siddhu is a London-based software engineer who was once described by somebody he annoyed as the epitome of clubbable brown gentlemanliness.

Writing DSLs using Scala. Part II - A simple matcher DSL

Wednesday, May 13, 2015

This is the second of a two-part series of blogposts that explain how Scala can be used to create powerful Domain Specific Languages (DSLs). Before you read this, please read the first part, where I describe the Scala features that allow us to build these DSLs.

In this post, I shall illustrate how to build a Scala DSL by building a simple matcher DSL that will allow users of the DSL to perform assertions — in essence, a simplified version of a testing DSL like Scalatest or Specs 2.

Notes on the Testing Framework
As I plan to build a simple subset of a testing DSL as part of this post, I couldn't use a testing DSL to test it as I shall be implementing some of the same verbs. So I've decided to use plain ol' JUnit.

Running the example code
You can clone the accompanying code project from Github here:

The first thing we shall implement (after having written the tests, obviously) is the should infix operator, which we can implement as an implicit class within a trait that we shall call SimpleMatcherDsl. This operator should take two operands:
* An object of any type on the LHS, and
* on the RHS, a matcher function that should take the left-hand operator and return a boolean (after presumably performing some sort of comparison between the left and right hand sides). If the matcher function returns false, the operation should fail with a TestFailedException.

The matcher function we wish to implement for this feature is, obviously, one called equal. In order to be passed in as the RHS of the should infix operator, he equal method should return a function with a Any=>Boolean signature (the fact that Scala functions are first-class values comes in handy here).

In the code snippet above, the equal method takes the right operand as an argument, while returning a function that takes the left operand as an argument. Upon invocation, the returned function will compare the rightOperand passed into the equal method against the leftOperand passed in as an argument to it.

And yes, that's all there is to it.

Note: All of the tests for this feature may be found in info.siddhuw.EqualityAssertionTest.scala file and the implementation itself is in info.siddhuw.SimpleMatcherDsl. The code at this point has been tagged blogpost-part-2-feature-1

This is the second of a two-part series of blogposts that explain how Scala can be used to create powerful Domain Specific Languages (DSLs). You can read the first part, where I describe the Scala features that allow us to build these DSLs, here.

In this post, I shall illustrate how to build a Scala DSL by building a simple matcher DSL that will allow users of the DSL to perform assertions — in essence, a simplified version of a testing DSL like Scalatest or Specs 2.

Notes on the Testing Framework
As I plan to build a simple subset of a testing DSL as part of this post, I couldn't use a testing DSL to test it as I shall be implementing some of the same verbs. So I've decided to use plain ol' JUnit.

Running the example code
You can clone the accompanying code project from Github here:

The first thing we shall implement (after having written the tests, obviously) is the should infix operator, which we can implement as an implicit class within a trait that we shall call SimpleMatcherDsl. This operator should take two operands:
* An object of any type on the LHS, and
* on the RHS, a matcher function that should take the left-hand operator and return a boolean (after presumably performing some sort of comparison between the left and right hand sides). If the matcher function returns false, the operation should fail with a TestFailedException.

The matcher function we wish to implement for this feature is, obviously, one called equal. In order to be passed in as the RHS of the should infix operator, he equal method should return a function with a Any=>Boolean signature (the fact that Scala functions are first-class values comes in handy here).

In the code snippet above, the equal method takes the right operand as an argument, while returning a function that takes the left operand as an argument. Upon invocation, the returned function will compare the rightOperand passed into the equal method against the leftOperand passed in as an argument to it.

And yes, that's all there is to it.

Note: All of the tests for this feature may be found in info.siddhuw.EqualityAssertionTest.scala file and the implementation itself is in info.siddhuw.SimpleMatcherDsl. The code at this point has been tagged blogpost-part-2-feature-1

DSL Feature: Inequality

The objective of this feature is to allow users of the DSL to perform assertions of inequality as shown below:

Pretty good, but as I'm sure you'll agree, not quite what we'd like to get to. Rather than a space between not and equal, we've ended up with a very unnatural parenthesis.

The nicer way

Note: The Scalatest code implements 'not equal' in a very similar fashion; my implementation, while far more limited, is drawn entirely from the way Scalatest implements it.

What we'd like to implement is a DSL specification that allows us to avoid the parentheses that plagued our simple approach.

Referring back to my previous blogpost, Scala allows the . membership operator to be replaced with a space. So, in our case, if not were an object, and equal were a method of the not object, we'd be able to replace not(equal(x)) with not.equal(x) or better still, not equal x.

To this end, let's implement a class called NotKeyword:

sealed class NotKeyword

Digression: Sealed classes
Sealed classes and traits are classes or traits that cannot be inherited from outside of the file in which they have been declared.

Then, let's create an object called not of type NotKeyword

val not = new NotKeyword

Now, our first impulse would be to implement an equal method in the NotKeyword class (it was certainly my first impulse).

But if we were to do that and try to compile the statement 12 should equal 14, we'd get a compilation error. This is because the Scala operator precedence rules lead to

12 should not equal 14

being parsed as

12.should(not)[...]

because it evaluates the operation from left to right. Now, the should method does not accept a NotKeyword as an argument; it expects a function with an Any=>Boolean signature.

We could fix our statement above to compile and work correct by changing it to:

12 should (not equal 14)

But this is even less readable than the simpler approach we took in the previous section.

To fix this, let's look once again at how the Scala compiler evaluated our expression. It took the object not in as an argument to the should method (i.e., as the second operand to the should operator). So, we need to do two things:
* Create a should method signature that accepts a NotKeyword object as an argument.
* Return an object of a different type (let's call it the NotKeywordResult) that has a method called equal which takes the right operand as an argument.

If you've stayed with me this far, you should now be the proud possessor of a pale Scalatest/Specs 2 imitation that serves no practical purpose whatsoever. However, the principles we applied here are analogous to those we'd need to apply in order to build less trivial DSLs.