Domain Specific Languages
Hiro Yoshioka March 15th, 2013 Rakuten Tower 2, Shinagawa, Tokyo, Japan
2
Chapter 4, Internal DSL
• Unlike external DSLs, you don’t need to learn about grammars and language parsing.
• Constrain – host language expression • Ruby • Lisp
• fluent interface vs API
3
4.1 Fluent and Command-Query APIs
• Method Chaining Processor p = new Processor(2, 2500, Processor.Type.i386); Disk d1 = new Disk(150, Disk.UNKNOWN_SPEED, null); Disk d2 = new Disk(75, 7200, Disk.Interface.SATA); return new Computer(p, d1, d2); Method Chaining computer() .processor() .cores(2) .speed(2500) .i386() .disk() .size(150) .disk() .size(75) .speed(7200) .sata() .end();
4
4.1 Fluent and Command-Query APIs
• Func]on Sequence computer() processor(); cores(2); speed(2500); i386(); disk(); size(150); disk(); size(75); speed(7200); sata();
5
4.1 Fluent and Command-Query APIs
6
4.1 Fluent and Command-Query APIs
• Command-‐query separa]on • Command: may change state, but not
return value • Query: does not change state • Name – without context
• Fluent: Name – context is important
7
4.2 The Need for a Parsing Layer
• Expression Builder • input: fluent interface • output: a sequence of command-‐query API
• Seman]c model • Separa]ng the Seman]c model from
Expression Builders • You can test them independently.
8
4.3 Using Functions
• func]on – most successful packaging, (also called subrou]ne, procedure, method)
computer(); processor(); cores(2); speed(2500); i386(); disk(); size(150); disk(); size(75); speed(7200); sata();
9
4.3 Using Functions • Method Chaining and Func]on Sequence
• Scope of the func]ons • Func]on Sequence: ensure the func]ons
resolve properly • global func]on – complica]ng
namespace and introducing global variables for parsing data. • Context Variables
• Method Chaining – avoids global • object scoping – avoid globalness,
extensibility • nested func]on – avoid context
variables
10
4.3 Using Functions
• nested func]on • combines func]ons by making func]on
calls arguments in higher level func]on calls.
• reflects the logical syntax tree of the DSL • change in evalua]on order • “(“, “)”, “,” are explicit. noise – Lisp
• (third(second(first))) ** I found typo
computer( processor( cores(2), speed(2500), i386 ), disk( size(150) ), disk( size(75), speed(7200), SATA ) );
11
4.3 Using Functions
• nested func]on • the hierarchic structure of the
configura]on is echoed by the language constructs themselves
• reflects the logical syntax tree of the DSL • evalua]on order • arguments are iden]fied by posi]on rather
than name
12
4.3 Using Functions
• hybrid • It uses Func]on Sequence, each computer
func]on uses Nested Func]on, each processor and disk is using Method Chaining.
• Advantages: • Func]on Sequence: each computer
defini]on well separated. • Nested Func]on: eliminates a Context
Variable. • Method Chaining: mul]ple op]onal
arguments
computer( processor() .cores(2) .speed(2500) .type(i386), disk() .size(150), disk() .size(75) .speed(7200) .iface(SATA) ); computer( processor() .cores(4) );
13
4.3 Using Functions
• hybrid • problems – punctua]onal confusion
• comma, periods, semicolons, …
14
4.4 Literal Collections
• literal list
-‐-‐ Java or C# computer( processor (...), disk(...), disk(...) ); -‐-‐ Ruby computer [ processor(...), disk(...), disk(...) ]
15
4.4 Literal Collections
• literal map • named parameters • symbol
• immutable, symbol lookup • :symbol (ruby syntax)
-‐-‐ Ruby computer(processor(:cores => 2, :type => :i386), disk(:size => 150), disk(:size => 75, :speed => 7200, :interface => :sata)) -‐-‐ Ruby 2.0 has a keyword arguments
16
4.4 Literal Collections
• symbol
-‐-‐ Ruby computer(processor(:cores => 2, :type => :i386), disk(:size => 150), disk(:size => 75, :speed => 7200, :interface => :sata))
17
4.5 Using Grammars to Choose Internal Elements
Structure BNF Consider
Mandatory list parent ::= first second third Nested Func+on (357)
Op]onal list parent ::= first maybeSecond? maybeThird?
Method Chaining (373), Literal Map (419)
Homogenous bag parent ::= child* Literal List (417), Func+on Sequence (351)
Hetrogenous bag parent ::= (this | that | theOther)* Method Chaining
Set n/a Literal Map
18
4.6 Closures
• lambdas, blocks, anonymous func]ons
#ruby... ComputerBuilder.build do |c| c.processor do |p| p.cores 2 p.i386 p.speed 2.2 end c.disk do |d| d.size 150 end c.disk do |d| d.size 75 d.speed 7200 d.sata end end
19
4.6 Closures
• Nested Closures • inline nes]ng • deferred evalua]on • limited-‐scope variables
#ruby... ComputerBuilder.build do |c| c.processor do |p| p.cores 2 p.i386 p.speed 2.2 end c.disk do |d| d.size 150 end c.disk do |d| d.size 75 d.speed 7200 d.sata end end
20
4.7 Parse Tree Manipulation
• parse tree manipula]on
ptg9438845
BinaryExpression
MemberAccess
Left
ConstantExpression
Right
Age
Member
aPerson
Expression
18
Value
Figure 4.2 A parse tree representation of aPerson.Age > 18
query in another query language, such as SQL. This is essentially what .NET’sLinq language does. Linq allows you to express many SQL queries in C#, whichmany programmers prefer.
The strength of Parse Tree Manipulation is in allowing you to write expressionsin the host language that can then be converted into different expressions thatpopulate the Semantic Model (159) in ways that are beyond just storing the closureitself.
In C#’s case above, this manipulation is done with an object model representa-tion of the parse tree. In Lisp’s case, this manipulation is done by macro transfor-mations on Lisp source code. Lisp is well suited to this because the structure ofits source code is very close to that of a syntax tree. Parse Tree Manipulation ismore widely used in Lisp for DSL work—so much so that Lispers often wail atthe lack of macros in other languages. My view is that manipulating an objectmodel of the parse tree in C# style is a more effective way of doing Parse TreeManipulation than Lisp macros—although this may be due to my lack of practicewith Lisp’s macro processing.
Whatever mechanism you use, the next question is how important Parse TreeManipulation is as a technique for DSLs. One very prominent use is in Linq—aMicrosoft technology that allows you to express query conditions in C# and turnthem into different query languages for various target data structures. In thisway, one C# query can be turned into SQL for relational databases and XPathfor XML structures, or kept in C# for in-memory C# structures. It’s essentiallya mechanism that allows application code to do runtime code translation,generating arbitrary code from C# expressions.
Parse Tree Manipulation is a powerful, but somewhat complex, technique thathasn’t been supported much by languages in the past, but these days has beengetting a lot more attention due to its support in C# 3 and Ruby. Since it’s rela-tively new (at least outside the Lisp world), it’s hard to evaluate how truly usefulit is. My current perception is that it’s a marginal technique—something that israrely needed but very handy on the occasions when that need arises. Translating
4.7 Parse Tree Manipulation 83
4: Implementingan Internal DSL
From the Library of Hiro Yoshioka
21
4.8 Annotation
• Auribute in C#
class Person
validates_length_of :last_name, :maximum => 30
22
4.9 Literal Extension
• Method Chaining • chain begins on a literal integer,
e.g. 5.days.ago • add methods to external library classes • Cons: methods globally
23
4.10 Reducing the Syntactic Noise
• Textual Polishing
24
4.11 Dynamic Reception
• handle an unexpected call by run]me to a special method. programmers can override the method to do other things. • method_missing in Ruby, • doesNotUnderstand in Smalltalk
• e.g., Rails Ac]ve record’s dynamic finders
25
4.12 Providing Some Type Checking
• sta]c type checking or not • type checking at compile ]me or run ]me • modern IDEs provide excellent support based
on sta]c typing
26
Yoshioka
Top Related