Embedded Typesafe DSLs for Java Jevgeni Kabanov Tech Lead, ZeroTurnaround  jevgeni@zeroturnaround.com
ABOUT ME ZeroTurnaround Tech Lead Our flagship product is JavaRebel  - a class reloading JVM plugin Double as the R&D lead of Webmedia, Ltd. Largest custom software developer in the Baltics Co-founder of Aranea Web Framework Personal blog at  dow.ngra.de
Based on Research a rticle “Embedded Typesafe Domain Specific Languages for Java” by Jevgeni Kabanov & Rein Raudjärv Published at  Principles and Practice of Programming in Java  ' 08
OUTLINE DSLs and Fluent Interface Case study: Building SQL queries Case study: Java bytecode engineering
Domain Specific Language Small  sub-language  that has very little overhead when expressing domain specic data and behaviour Can be A fully implemented language A specialised API that looks like a sublanguage but still written using some general-purpose language  –  embedded DSL
Why bother? Low overhead You need to write less It is easier to understand Domain experts can understand it  Type safety You can be sure that certain errors won’t occur You need to write less tests You can use type info to add features
Fluent Interface customer.newOrder()  .with(6, "TAL")  .with(5, "HPK").skippable()  .with(3, "LGV") .priorityRush();
Java 5 features and EDSLs Java 5 has  Parametric polymorphism (generics) Static method import Java 5 doesn’t have Closures or first-class functions Operator overloading Variable/parameter type inference
BUILDING SQL QUERIES
SQL Example (4 errors) StringBuffer sql =  new   StringBuffer() ; sql.append( "SELECT o.sum,(SELECT first_name,last_name" ); sql.append( "  FROM person p" ); sql.append( "  WHERE o.person_id=p.id) AS client" ); sql.append( " FROM order o" ); sql.append( "WHERE o.id = " +orderId); sql.append( "  AND o.status_code IN (?,?)" ); PreparedStatement stmt = conn.prepareStatement(sql.toString()); stmt.setString(1,  "PAID" ); //...
Typesafe SQL Example Person p =  new   Person() ;   List<Tuple3<String, Integer, Date>> rows = new   QueryBuilder(datasource) .from(p) .where( gt(p. height , 170)) .select(p. name , p. height , p. birthday ) .list(); for   (Tuple3<String, Integer, Date> row : rows) { String name = row. v1 ; Integer height = row. v2 ; Date birthday = row. v3 ; System. out .println( name +  &quot; &quot;  + height +  &quot; &quot;  + birthday); }
Tuples R eturn  tuples  that have precisely the selected data with types known ahead Tuple types are  inferred  from the select expression (column) types
Tuple2 public   class   Tuple2<T1, T2>  implements   Tuple { public   final   T1  v1 ; public   final   T2  v2 ; public   Tuple2(T1 v1, T2 v2) { this . v1  = v1; this . v2   = v2; } public   T1 v1() {  return   v1 ; } public   T2 v2() {  return   v2 ; } }
Typesafe SQL Example Person p =  new   Person() ;   List<Tuple3<String, Integer, Date>> rows = new   QueryBuilder(datasource) .from(p) .where( gt(p. height , 170)) .select(p. name , p. height , p. birthday ) .list(); for   (Tuple3<String, Integer, Date> row : rows) { String name = row. v1 ; Integer height = row. v2 ; Date birthday = row. v3 ; System. out .println( name +  &quot; &quot;  + height +  &quot; &quot;  + birthday); }
Typesafe Metadata Metadata used by your DSL should include   compile-time type information. We make use of pregenerated metadata dictionary that contains type information about tables and columns
Metadata dictionary public   class   Person   implements   Table   { public   String   getName() {   return   &quot;person&quot; ;  } ; public   Column<Person, String> name = new   Column<Person, String>( this ,  &quot;name&quot; , String . class ) ; public   Column<Person, Integer> height = new   Column<Person, Integer>( this ,  &quot;height&quot; , Integer. class ) ; public   Column<Person, Date> birthday = new   Column<Person, Date>( this ,  &quot;birthday&quot; , Date. class ) ; }
Typesafe SQL Example Person p =  new   Person() ;   List<Tuple3<String, Integer, Date>> rows = new   QueryBuilder(datasource) .from(p) .where( gt(p. height , 170)) .select(p. name , p. height , p. birthday ) .list(); for   (Tuple3<String, Integer, Date> row : rows) { String name = row. v1 ; Integer height = row. v2 ; Date birthday = row. v3 ; System. out .println( name +  &quot; &quot;  + height +  &quot; &quot;  + birthday); }
Restricting Syntax At any moment of time the DSL builder should have precisely the methods allowed in the current state. SQL query builders allow  from ,  where  and  select  to be called once and only once only in valid order
QueryBuilder class   QueryBuilder   extends   Builder   { ... <T  extends   Table> FromBuilder<T> from(T table); }
FromBuilder class   FromBuilder<T  extends  Table> extends   Builder { ... <C1> SelectBuilder1<T, C1> select(Col<T, C1> c1); <C1, C2> SelectBuilder2<T, C1, C2> select(Col<T, C1> c1, Col<T, C2> c2); ... }
SelectBuilder class   SelectBuilder2<T   extends   Table,C1,C2>  extends   SelectBuilder<T> { ... List<Tuple2<C1,C2>> list(); ... }
Typesafe SQL Example Person p =  new   Person() ;   List<Tuple3<String, Integer, Date>> rows = new   QueryBuilder(datasource) .from(p) .where( gt(p. height , 170)) .select(p. name , p. height , p. birthday ) .list(); for   (Tuple3<String, Integer, Date> row : rows) { String name = row. v1 ; Integer height = row. v2 ; Date birthday = row. v3 ; System. out .println( name +  &quot; &quot;  + height +  &quot; &quot;  + birthday); }
Hierarchical Expressions Use method chaining when you need context  static functions when you need hierarchy and extensibility or( eq(p.name, &quot;Peter&quot;), gt(p.height, 170) )
Expression public   interface   Expression<E> { String getSqlString(); List<Object> getSqlArguments(); Class<E> getType(); }
Expressions class   Expressions { Expr<Bool> and(Expr<Bool>... e) <E> Expr<Bool> eq(Expr<E> e1, Expr<E> e2) Expr<Bool> like(Expr<?> e, Expr<String> pattern) <E> Expr<E> constant(E value) Expr<String> concat(Expr... e) ... }
Closures interface   Closure   {   void   apply(Builder b); } class   SelectBuilderC2<C1,C2> extends   SelectBuilder { SelectBuilderC2<C1,C2> closure(Closure closure) { closure.apply( this ); return   this ; } } }
Unsafe Assumptions Allow the user to do type unsafe actions, but make sure he has to document his assumptions. Expression<Integer> count = unchecked(Integer. class , &quot;util.countChildren(id)&quot; );
Used Patterns Restricting syntax (builders allow  from ,  where  and  select  to be called only once) Typesafe metadata (data dictionary) Hierarchical Expressions  (with method chaining for main syntax) Unsafe assumptions (unchecked expressions declare the expected type) Closures for mixing with the control flow
Case Study 2: Typesafe Bytecode
Java  Class Definition Modifiers, name, super class, interfaces Enclosing class reference Annotation* Inner class* Name Field* Modifiers, name, type Annotation* Method* Modifiers, name, return and parameter types Annotation* Compiled code
Java Execution Model Local variables Operand stack Execution stack Frame Calling a method Throwing an exception
Instruction Example opcode argumen ts apply Operands stack when applying the instruction : Instruction :
Bytecode Engineering ASM – Java bytecode engineering library Visitor-based API Tree-based API Completely untyped Produced bytecode is only verified  at  runtime
Hello, World! public   class   HelloWorld { public   static   void   main(String[] args) { System. out .println( &quot;Hello, World!&quot; ); } }
Hello, World! in Bytecode public   class   HelloWorld { public   <init>()V ALOAD 0 INVOKESPECIAL  Object.<init>()V RETURN public   static   main([LString;)V GETSTATIC System.out : LPrintStream; LDC  &quot;Hello, World!&quot; INVOKEVIRTUAL PrintStream.println(LString;)V RETURN }
Hello, World! in ASM ClassWriter cw =  new   ClassWriter(0); MethodVisitor  mv; cw.visit( V1_6 ,  ACC_PUBLIC  +  ACC_SUPER ,  &quot;HelloWorld&quot; ,  null ,  &quot;java/lang/Object&quot; ,  null ); { mv = cw.visitMethod( ACC_PUBLIC  +  ACC_STATIC ,  &quot;main&quot; ,  &quot;([Ljava/lang/String;)V&quot; ,  null ,  null ); mv.visitCode(); mv.visitFieldInsn( GETSTATIC ,  &quot;java/lang/System&quot; ,  &quot;out&quot; ,  &quot;Ljava/io/PrintStream;&quot; ); mv.visitLdcInsn( &quot;Hello, World!&quot; ); mv.visitMethodInsn( INVOKEVIRTUAL ,  &quot;java/io/PrintStream&quot; ,  &quot;println&quot; ,  &quot;(Ljava/lang/String;)V&quot; ); mv.visitInsn( RETURN ); mv.visitMaxs(2, 1); mv.visitEnd(); } cw.visitEnd();
Hello, World! in DSL
Possible mistakes Not enough stack elements for the instruction Stack elements have the wrong type Local variables have the wrong type Using illegal modifiers or opcodes
Similar  Patterns Typesafe metadata (class literals) Closures for mixing with the control flow
Different Patterns Restricting syntax  H ide methods that consume more stack slots than available Unsafe assumptions Deprecate methods instead of omitting them Allow assuming stack slot and local variable types
Tracking stack We generate classes MethodBuilderX, where X ranges from 0 to N Each represents a state with X stack slots initialized Stack consuming operations are only available in builders which have enough stack slots initialized
Tracking stack
Hello, World! in DSL
Type History You can accumulate type history as a type list and use it to reject actions that do not fit with that history In addition to tracking the current stack size we track each stack slot type using a list of generic type variables
Tracking stack types
Tracking stack types
Hello, World! in DSL
Unsafe Assumptions Allow the user to do type unsafe actions, but make sure he has to document his assumptions.
Unsafe Assumptions
Reusable genSayHello() private   static   void   genSayHello(MethodBuilder0 mb) { mb .assumePush(String. class ) .getStatic(System. class ,  &quot;out&quot; ,  PrintStream . class ) .swap()  .invoke()   .param(String. class ) .virtVoid(PrintStream. class ,  &quot;println&quot; );  }
Questions

Embedded Typesafe Domain Specific Languages for Java

  • 1.
    Embedded Typesafe DSLsfor Java Jevgeni Kabanov Tech Lead, ZeroTurnaround jevgeni@zeroturnaround.com
  • 2.
    ABOUT ME ZeroTurnaroundTech Lead Our flagship product is JavaRebel - a class reloading JVM plugin Double as the R&D lead of Webmedia, Ltd. Largest custom software developer in the Baltics Co-founder of Aranea Web Framework Personal blog at dow.ngra.de
  • 3.
    Based on Researcha rticle “Embedded Typesafe Domain Specific Languages for Java” by Jevgeni Kabanov & Rein Raudjärv Published at Principles and Practice of Programming in Java ' 08
  • 4.
    OUTLINE DSLs andFluent Interface Case study: Building SQL queries Case study: Java bytecode engineering
  • 5.
    Domain Specific LanguageSmall sub-language that has very little overhead when expressing domain specic data and behaviour Can be A fully implemented language A specialised API that looks like a sublanguage but still written using some general-purpose language – embedded DSL
  • 6.
    Why bother? Lowoverhead You need to write less It is easier to understand Domain experts can understand it Type safety You can be sure that certain errors won’t occur You need to write less tests You can use type info to add features
  • 7.
    Fluent Interface customer.newOrder() .with(6, &quot;TAL&quot;) .with(5, &quot;HPK&quot;).skippable() .with(3, &quot;LGV&quot;) .priorityRush();
  • 8.
    Java 5 featuresand EDSLs Java 5 has Parametric polymorphism (generics) Static method import Java 5 doesn’t have Closures or first-class functions Operator overloading Variable/parameter type inference
  • 9.
  • 10.
    SQL Example (4errors) StringBuffer sql = new StringBuffer() ; sql.append( &quot;SELECT o.sum,(SELECT first_name,last_name&quot; ); sql.append( &quot; FROM person p&quot; ); sql.append( &quot; WHERE o.person_id=p.id) AS client&quot; ); sql.append( &quot; FROM order o&quot; ); sql.append( &quot;WHERE o.id = &quot; +orderId); sql.append( &quot; AND o.status_code IN (?,?)&quot; ); PreparedStatement stmt = conn.prepareStatement(sql.toString()); stmt.setString(1, &quot;PAID&quot; ); //...
  • 11.
    Typesafe SQL ExamplePerson p = new Person() ; List<Tuple3<String, Integer, Date>> rows = new QueryBuilder(datasource) .from(p) .where( gt(p. height , 170)) .select(p. name , p. height , p. birthday ) .list(); for (Tuple3<String, Integer, Date> row : rows) { String name = row. v1 ; Integer height = row. v2 ; Date birthday = row. v3 ; System. out .println( name + &quot; &quot; + height + &quot; &quot; + birthday); }
  • 12.
    Tuples R eturn tuples that have precisely the selected data with types known ahead Tuple types are inferred from the select expression (column) types
  • 13.
    Tuple2 public class Tuple2<T1, T2> implements Tuple { public final T1 v1 ; public final T2 v2 ; public Tuple2(T1 v1, T2 v2) { this . v1 = v1; this . v2 = v2; } public T1 v1() { return v1 ; } public T2 v2() { return v2 ; } }
  • 14.
    Typesafe SQL ExamplePerson p = new Person() ; List<Tuple3<String, Integer, Date>> rows = new QueryBuilder(datasource) .from(p) .where( gt(p. height , 170)) .select(p. name , p. height , p. birthday ) .list(); for (Tuple3<String, Integer, Date> row : rows) { String name = row. v1 ; Integer height = row. v2 ; Date birthday = row. v3 ; System. out .println( name + &quot; &quot; + height + &quot; &quot; + birthday); }
  • 15.
    Typesafe Metadata Metadataused by your DSL should include compile-time type information. We make use of pregenerated metadata dictionary that contains type information about tables and columns
  • 16.
    Metadata dictionary public class Person implements Table { public String getName() { return &quot;person&quot; ; } ; public Column<Person, String> name = new Column<Person, String>( this , &quot;name&quot; , String . class ) ; public Column<Person, Integer> height = new Column<Person, Integer>( this , &quot;height&quot; , Integer. class ) ; public Column<Person, Date> birthday = new Column<Person, Date>( this , &quot;birthday&quot; , Date. class ) ; }
  • 17.
    Typesafe SQL ExamplePerson p = new Person() ; List<Tuple3<String, Integer, Date>> rows = new QueryBuilder(datasource) .from(p) .where( gt(p. height , 170)) .select(p. name , p. height , p. birthday ) .list(); for (Tuple3<String, Integer, Date> row : rows) { String name = row. v1 ; Integer height = row. v2 ; Date birthday = row. v3 ; System. out .println( name + &quot; &quot; + height + &quot; &quot; + birthday); }
  • 18.
    Restricting Syntax Atany moment of time the DSL builder should have precisely the methods allowed in the current state. SQL query builders allow from , where and select to be called once and only once only in valid order
  • 19.
    QueryBuilder class QueryBuilder extends Builder { ... <T extends Table> FromBuilder<T> from(T table); }
  • 20.
    FromBuilder class FromBuilder<T extends Table> extends Builder { ... <C1> SelectBuilder1<T, C1> select(Col<T, C1> c1); <C1, C2> SelectBuilder2<T, C1, C2> select(Col<T, C1> c1, Col<T, C2> c2); ... }
  • 21.
    SelectBuilder class SelectBuilder2<T extends Table,C1,C2> extends SelectBuilder<T> { ... List<Tuple2<C1,C2>> list(); ... }
  • 22.
    Typesafe SQL ExamplePerson p = new Person() ; List<Tuple3<String, Integer, Date>> rows = new QueryBuilder(datasource) .from(p) .where( gt(p. height , 170)) .select(p. name , p. height , p. birthday ) .list(); for (Tuple3<String, Integer, Date> row : rows) { String name = row. v1 ; Integer height = row. v2 ; Date birthday = row. v3 ; System. out .println( name + &quot; &quot; + height + &quot; &quot; + birthday); }
  • 23.
    Hierarchical Expressions Usemethod chaining when you need context static functions when you need hierarchy and extensibility or( eq(p.name, &quot;Peter&quot;), gt(p.height, 170) )
  • 24.
    Expression public interface Expression<E> { String getSqlString(); List<Object> getSqlArguments(); Class<E> getType(); }
  • 25.
    Expressions class Expressions { Expr<Bool> and(Expr<Bool>... e) <E> Expr<Bool> eq(Expr<E> e1, Expr<E> e2) Expr<Bool> like(Expr<?> e, Expr<String> pattern) <E> Expr<E> constant(E value) Expr<String> concat(Expr... e) ... }
  • 26.
    Closures interface Closure { void apply(Builder b); } class SelectBuilderC2<C1,C2> extends SelectBuilder { SelectBuilderC2<C1,C2> closure(Closure closure) { closure.apply( this ); return this ; } } }
  • 27.
    Unsafe Assumptions Allowthe user to do type unsafe actions, but make sure he has to document his assumptions. Expression<Integer> count = unchecked(Integer. class , &quot;util.countChildren(id)&quot; );
  • 28.
    Used Patterns Restrictingsyntax (builders allow from , where and select to be called only once) Typesafe metadata (data dictionary) Hierarchical Expressions (with method chaining for main syntax) Unsafe assumptions (unchecked expressions declare the expected type) Closures for mixing with the control flow
  • 29.
    Case Study 2:Typesafe Bytecode
  • 30.
    Java ClassDefinition Modifiers, name, super class, interfaces Enclosing class reference Annotation* Inner class* Name Field* Modifiers, name, type Annotation* Method* Modifiers, name, return and parameter types Annotation* Compiled code
  • 31.
    Java Execution ModelLocal variables Operand stack Execution stack Frame Calling a method Throwing an exception
  • 32.
    Instruction Example opcodeargumen ts apply Operands stack when applying the instruction : Instruction :
  • 33.
    Bytecode Engineering ASM– Java bytecode engineering library Visitor-based API Tree-based API Completely untyped Produced bytecode is only verified at runtime
  • 34.
    Hello, World! public class HelloWorld { public static void main(String[] args) { System. out .println( &quot;Hello, World!&quot; ); } }
  • 35.
    Hello, World! inBytecode public class HelloWorld { public <init>()V ALOAD 0 INVOKESPECIAL Object.<init>()V RETURN public static main([LString;)V GETSTATIC System.out : LPrintStream; LDC &quot;Hello, World!&quot; INVOKEVIRTUAL PrintStream.println(LString;)V RETURN }
  • 36.
    Hello, World! inASM ClassWriter cw = new ClassWriter(0); MethodVisitor mv; cw.visit( V1_6 , ACC_PUBLIC + ACC_SUPER , &quot;HelloWorld&quot; , null , &quot;java/lang/Object&quot; , null ); { mv = cw.visitMethod( ACC_PUBLIC + ACC_STATIC , &quot;main&quot; , &quot;([Ljava/lang/String;)V&quot; , null , null ); mv.visitCode(); mv.visitFieldInsn( GETSTATIC , &quot;java/lang/System&quot; , &quot;out&quot; , &quot;Ljava/io/PrintStream;&quot; ); mv.visitLdcInsn( &quot;Hello, World!&quot; ); mv.visitMethodInsn( INVOKEVIRTUAL , &quot;java/io/PrintStream&quot; , &quot;println&quot; , &quot;(Ljava/lang/String;)V&quot; ); mv.visitInsn( RETURN ); mv.visitMaxs(2, 1); mv.visitEnd(); } cw.visitEnd();
  • 37.
  • 38.
    Possible mistakes Notenough stack elements for the instruction Stack elements have the wrong type Local variables have the wrong type Using illegal modifiers or opcodes
  • 39.
    Similar PatternsTypesafe metadata (class literals) Closures for mixing with the control flow
  • 40.
    Different Patterns Restrictingsyntax H ide methods that consume more stack slots than available Unsafe assumptions Deprecate methods instead of omitting them Allow assuming stack slot and local variable types
  • 41.
    Tracking stack Wegenerate classes MethodBuilderX, where X ranges from 0 to N Each represents a state with X stack slots initialized Stack consuming operations are only available in builders which have enough stack slots initialized
  • 42.
  • 43.
  • 44.
    Type History Youcan accumulate type history as a type list and use it to reject actions that do not fit with that history In addition to tracking the current stack size we track each stack slot type using a list of generic type variables
  • 45.
  • 46.
  • 47.
  • 48.
    Unsafe Assumptions Allowthe user to do type unsafe actions, but make sure he has to document his assumptions.
  • 49.
  • 50.
    Reusable genSayHello() private static void genSayHello(MethodBuilder0 mb) { mb .assumePush(String. class ) .getStatic(System. class , &quot;out&quot; , PrintStream . class ) .swap() .invoke() .param(String. class ) .virtVoid(PrintStream. class , &quot;println&quot; ); }
  • 51.