31
DSL DESIGN IN SCALA Z ACK GRANNAN

DSL Design in Scala

Embed Size (px)

DESCRIPTION

Scala provides expressive syntax and many language features that make it easy to write natural DSLs. In this talk I will examine Scala features that are helpful for creating DSLs and talk about how you can use Scala to develop an intuitive DSL that is at the same level of abstraction as your problem domain.

Citation preview

Page 1: DSL Design in Scala

DSL DESIGN IN SCALA ZACK GRANNAN

Page 2: DSL Design in Scala

WHAT IS A DSL

Language tailored for a specific Domain

Many Types and Sizes SQL, Matlab, Latex are big ones

Page 3: DSL Design in Scala

EMBEDDED VS STAND-ALONE DSL

Stand-Alone: •  Is a full language itself (e.g

HTML) Embedded:

•  Built on top of existing language (e.g ScalaTest)

Page 4: DSL Design in Scala

STAND ALONE Pros •  Ultimate Freedom

Cons •  Takes forever to develop. Don’t want to

reinvent the wheel •  Difficult for others to learn and use (see

VimScript)

Page 5: DSL Design in Scala

EMBEDDED

Pros •  Take advantage of parent language •  Much easier to develop

Cons •  Forced to use syntax of parent

language •  Often limited by parent language

Page 6: DSL Design in Scala

DEEP AND SHALLOW EMBEDDED DSL •  Shallow Embedded DSL

•  DSL Expressions are converted immediately into non-DSL instructions in parent langauge

•  Deep Embedded DSL

•  DSL Expressions are converted into data structure (think AST). Structure can be executed, or modified.

•  Maybe more work that shallow embedded DSL, but more powerful

Page 7: DSL Design in Scala

WHY CREATE A DSL

Problem in Domain

Procedure in Domain

Solution in Domain

Procedure in Language

Solution in Language

A DSL is the Ultimate Abstraction – Paul Hudak

Page 8: DSL Design in Scala

WHY CREATE A DSL

Problem in Domain

Procedure in Domain

Solution in Domain

A DSL is the Ultimate Abstraction – Paul Hudak

Page 9: DSL Design in Scala

WHAT MAKES A GOOD DSL

•  Syntax, Semantics match Domain •  More Expressive in Domain •  Less Powerful outside Domain Result: Cleaner Code, Fewer Bugs

Page 10: DSL Design in Scala

WHEN SHOULD YOU MAKE A DSL?

Almost Always

Page 11: DSL Design in Scala

WHY SCALA IS GOOD

•  Syntactic Sugar •  Infix, Postfix, Prefix, Symbolic

Operators •  Implicit Conversion •  Functional, Typesafe •  Macros

Page 12: DSL Design in Scala

SYNTACTIC SUGAR Semicolons are optional, as well as periods and parenthesis (in some cases). () and {} can be interchanged  result.shouldBe(3) result shouldBe 3!

 def unless(expr: Boolean)(perform: () => Any) {!

if (!expr) perform()!

}!

!

unless (2 == 1) {! () => println("Hello World")!

}// Outputs “Hello World”!

 

Page 13: DSL Design in Scala

INFIX OPERATORS / SYMBOLIC OPERATORS Any method that takes one parameter is an infix operator a + b = a.+(b)! Symbolic operators can be used (and abused) trait Expr!

case class Lt(a: Expr, b: Expr) extends Expr!

case class IntExpr(int: Int) extends Expr {!

def < (other: IntExpr) = Lt(this, other)!

}!

!

IntExpr(1) < IntExpr(2) // Lt(IntExpr(1),IntExpr(2))!

Page 14: DSL Design in Scala

POSTFIX AND PREFIX trait MyBool {!

def inverse : MyBool!

def unary_! : MyBool!

}!

!

case object True extends MyBool {!

def unary_! = False!

def inverse = False!

}!

!

case object False extends MyBool {!

def unary_! = True!

def inverse = True!

}!

!

println(!True) // Outputs False!

println(False inverse) // Outputs True

Page 15: DSL Design in Scala

IMPLICIT CONVERSION case class Apples(amount: Int) {!

override def toString = s"There are $amount apples"!

}!

!

implicit class AppleInt(num: Int) {!

def apples = Apples(num)!

}!

!

println(10 apples) // “There are 10 apples”

Page 16: DSL Design in Scala

IMPLICIT CONVERSION This is used to great effect in some libraries. scala.concurrent.duration val d = 5 millis!

val d2 = d * 2.5!

val d3 = d2 + 1.second!

!

Builtin:!

val range = 1 to 10

Page 17: DSL Design in Scala

MACROS •  Scala code that writes Scala code •  Much better than C Macros •  Blackbox Macro: Safe Macro

•  Type checking can be done before macro invocation

•  Whitebox Macro: Powerful Macro •  Macro can introduce new types

Page 18: DSL Design in Scala

UNLESS MACRO def unless(condition: Boolean)(thenExpr: Any): Unit = macro unlessImpl!

!

def unlessImpl(c: Context)(condition: c.Expr[Boolean])(thenExpr: c.Expr[Any]): c.universe.If = {!

import c.universe._!

q"if (!($condition)) {$thenExpr}”!

}!

!

---!!

unless (2 == 1) {!

println("Hello World")!

}!

!

if (!(2 == 1)) {! println("Hello World")!}

Page 19: DSL Design in Scala

CUSTOM COMPILE-TIME ERRORS case class NonSpaceString(str: String)!

object NonSpaceString {!

implicit def fromString(s: String): NonSpaceString = macro makeNSString!

!

def makeNSString(c: Context)(s: c.Expr[String]) = {!

import c.universe._!

s match {!

case Expr(Literal(Constant(field))) =>!

val fieldString = showRaw(field)!

if (fieldString contains ' ') {!

throw new Exception(s"$fieldString contains a space character")!

}!

q"new NonSpaceString($fieldString)"!

}!

}!

}

Page 20: DSL Design in Scala

CUSTOM COMPILE-TIME ERRORS import NonSpaceString._!

object Cl {!

def printNsString(s: NonSpaceString) {!

println(s.str)!

}!

!

def main(args: Array[String]) {!

printNsString("abc") // NonSpaceString!

!

printNsString("abc d") // Compile-time Error!

}!

}!

!

cl.scala:9: error: exception during macro expansion:!

java.lang.Exception: abc d contains a space character!

!at NonSpaceString$.makeNSString(mk.scala:16)!

!

printNsString("abc d")!

Page 21: DSL Design in Scala

PUTTING IT ALL TOGETHER - RULE DSL Condition Action

Rule

Page 22: DSL Design in Scala

CONDITION DEFINITION sealed trait Condition {!

def or (condition: Condition) = Or(this, condition)!

def and (condition: Condition) = And(this, condition)!

def unary_! = Not(this)!

}!

!

case class Or(c1: Condition, c2: Condition) extends Condition!

case class And(c1: Condition, c2: Condition) extends Condition!

case class Not(c: Condition) extends Condition!

case class DependentCondition(f: () => Boolean) extends Condition!

case object True extends Condition!

case object False extends Condition!

!

(True and !False) or (False or True)!

// Or(And(True,Not(False)),Or(False,True))!

!

Page 23: DSL Design in Scala

CONDITION DEFINITION def eval(condition: Condition): Boolean = condition match {!

case And(c1, c2) => eval(c1) && eval(c2)!

case Or(c1, c2) => eval(c1) || eval(c2)!

case Not(c) => !eval(c)!

case DependentCondition(f) => f()!

case True => true!

case False => false!

}!

Page 24: DSL Design in Scala

ACTION DEFINITION sealed trait Action {!

def andThen(action2: Action) = Then(this, action2)!

}!

!

case class Then(action1: Action, action2: Action) extends Action!

case class FunctionAction(function: () => Any) extends Action!

!

implicit def funcToAction(fun: () => Any) = {!

FunctionAction(fun)!

}!

!

def sayHello() { println("Hello") }!

def sayWorld() { println("World") }!

!

(sayHello _) andThen (sayWorld _) !

// Then(FunctionAction(<function0>),FunctionAction(<function0>))!

!

Page 25: DSL Design in Scala

ACTION DEFINITION def eval(action: Action) {!

case Then(a1, a2) => eval(a1); eval(a2)!

case FunctionAction(f) => f()!

}!

Page 26: DSL Design in Scala

RULE DEFINITION trait Rule {!

def condition: Condition!

def action: Action!

def elseAction: Option[Action] = None!

}!

!

case class IfRule(condition: Condition, action: Action) extends Rule {!

def otherwise(elseAction: Action) =!

IfElseRule(condition, action, elseAction)!

}!

!

case class IfElseRule(condition: Condition, action: Action, elseAct: Action) extends Rule {!

override def elseAction = Some(elseAct)!

}

Page 27: DSL Design in Scala

RULE DEFINITION def eval(rule: Rule) {!

eval(rule.condition) match {!

case true => eval(rule.action)!

case false => rule.elseAction.foreach(eval)!

}!

}!

!

Page 28: DSL Design in Scala

RULE DEFINITION def when(condition: Condition)(action: Action) = !

IfRule(condition, action)!

!

def unless(condition: Condition)(action: Action) = !

IfRule(Not(condition), action)!

!

when (True and False) {!

() => println("Won't see this")!

} otherwise {!

() => println("Will see this")!

}!

!

IfElseRule(!

And(True,False),!

FunctionAction(<function0>),!

FunctionAction(<function0>)!

)!

Page 29: DSL Design in Scala

RULE DEFINITION eval {!

when (True and False) {!

() => println("Won't see this")!

} otherwise {!

() => println("Will see this")!

}!

}!

Will see this

Page 30: DSL Design in Scala

QUESTIONS?

Page 31: DSL Design in Scala

WHITEBOX MACRO object Macros {!

def makeInterface(s: String*): Any = macro makeInterfaceImpl!

def makeInterfaceImpl(c: Context)(s: c.Expr[String]*) = {!

import c.universe._!

val getters = s.map {!

case Expr(Literal(Constant(field))) =>!

val fieldName = showRaw(field)!

val tt = TermName(fieldName)!

val upper = fieldName.toUpperCase!

q"""def $tt = $upper"""!

}!

!

c.Expr(q"""!

case object Custom {!

..$getters!

};!

Custom""")!

}!

}!

!

val custom = !

makeInterface("abc", "def”)!

println(custom.abc) !

// Outputs "ABC”!

---------------------------------!

!

Expr[Nothing]({!

case object Custom extends scala.Product with scala.Serializable {!

def <init>() = {!

super.<init>();!

()!

};!

def abc = "ABC";!

def `def` = "DEF"!

};!

Custom!

})!

!