Download pdf - DSL Design in Scala

Transcript
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!

})!

!


Recommended