Wednesday, August 17, 2016

DSL example with Scala

As you know I just started to learn Scala few months ago, and I have to say that each time I am developing in Scala I see in it a lot of powerful utilities.

 Today I want to implement a simple DSL (Domain Specific Language).

Imagine we want to design a language to communication our software with an external hardware, in this case a robot. We are going to be able to communicate with our robot in this way:

val robot = newRobot(0, 0)
robot at 30.km_hour towards (20, 15) go
robot at 60.km_hour towards (60, 15) go
robot at 2.meters_second towards (22, 17) go
view raw RobotApp.scala hosted with ❤ by GitHub

There are some key concepts applied in the code showed above:

  • Syntax sugar. In Scala there are several situations where ',' and '()' are optionals. For example when the method has only one parameter.
  • Builder pattern. To make a fluent Api you can use the Builder pattern which consists in return the object 'this', then in this way, you can concatenate the calls to the different methods in one line. 
  • Implicit conversions. Implicit Conversions are a set of methods that Scala tries to apply when it encounters an object of the wrong type being used. To use implicit conversions, you need to import the class that contains the implicit methods to do the conversions.
The main application.
import Robot._
import Speed._
object RobotApp {
def main(args: Array[String]): Unit = {
// scala style
val robot = newRobot(0, 0)
robot at 30.km_hour towards (20, 15) go
robot at 60.km_hour towards (60, 15) go
robot at 2.meters_second towards (22, 17) go
// java style
/*
Robot robot = Robot.newRobot(0, 0);
robot.at(new Speed(30).km_hour()).towards(20, 20).go();
*/
}
}
view raw RobotApp1.scala hosted with ❤ by GitHub

The Robot class.
import com.hdbandit.dsl.Speed._
object Robot {
def newRobot(initialXposition: Int = 0, initialYposition: Int = 0): Robot = new Robot(initialXposition, initialYposition)
}
class Robot(initialXposition: Int, initialYposition: Int) {
private var previousX = -1
private var previousY = -1
private var xPosition = initialXposition
private var yPosition = initialYposition
private var speed = 0.km_hour
def at(s: Speed): Robot = {
speed = Speed(s.value)
this
}
def towards(coordinate: (Int, Int)): Robot = {
previousX = xPosition
previousY = yPosition
xPosition = coordinate._1
yPosition = coordinate._2
println(s"Moving the robot from (x=$previousX, y=$previousY) to (x=$xPosition, y=$yPosition) with speed: $speed")
this
}
def go(): Unit = {
println(s"Moving the robot from (x=$previousX, y=$previousY) to (x=$xPosition, y=$yPosition) with speed: $speed")
}
}
view raw Robot.scala hosted with ❤ by GitHub

Finally the Speed class that enriches the Int type.
object Speed {
implicit def Int2Speed(value: Int): Speed = new Speed(value)
}
case class Speed(value: Int) {
def km_hour: Speed = this
def milles_hour: Speed = this
def meters_second: Speed = this
}
view raw Speed.scala hosted with ❤ by GitHub
You can find all the code in the my github account here