dsl design in scala
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.TRANSCRIPT
![Page 1: DSL Design in Scala](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/1.jpg)
DSL DESIGN IN SCALA ZACK GRANNAN
![Page 2: DSL Design in Scala](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/2.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/3.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/4.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/5.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/6.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/7.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/8.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/9.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/10.jpg)
WHEN SHOULD YOU MAKE A DSL?
Almost Always
![Page 11: DSL Design in Scala](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/11.jpg)
WHY SCALA IS GOOD
• Syntactic Sugar • Infix, Postfix, Prefix, Symbolic
Operators • Implicit Conversion • Functional, Typesafe • Macros
![Page 12: DSL Design in Scala](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/12.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/13.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/14.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/15.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/16.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/17.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/18.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/19.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/20.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/21.jpg)
PUTTING IT ALL TOGETHER - RULE DSL Condition Action
Rule
![Page 22: DSL Design in Scala](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/22.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/23.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/24.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/25.jpg)
ACTION DEFINITION def eval(action: Action) {!
case Then(a1, a2) => eval(a1); eval(a2)!
case FunctionAction(f) => f()!
}!
![Page 26: DSL Design in Scala](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/26.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/27.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/28.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/29.jpg)
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](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/30.jpg)
QUESTIONS?
![Page 31: DSL Design in Scala](https://reader033.vdocuments.site/reader033/viewer/2022052901/556491d4d8b42a5f6c8b4e9d/html5/thumbnails/31.jpg)
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!
})!
!