dsls in scala & db4o
Post on 05-Jul-2015
1.657 Views
Preview:
DESCRIPTION
TRANSCRIPT
present DSLs in Scala for MSI at HSMA
Scala
Scala
Marcus Körner (@atla_) Johannes Wachter (@jow85)
[1]
2
Grande Cinnamon Dolce Latte with tripple shot with non-fat milk topped with whipped cream
[2]
3
place orders (
new Order to buy(100 sharesOf ”IBM”)
limitPrice 300
allOrNone
using premiumPricing,
new Order to sell(200 bondsOf ”CISCO”)
limitPrice 300
allOrNone
using {
(qty, unit) => qty * unit - 500
}
)
Beispiel aus [DSLSINACTION]
[3]
Was sind DSLs
Warum Scala?
DSL Beispiele in Scala
DB4O DSL
Agenda 4
Was sind DSLs?
[4]
DSL
˃ Domain Specific Language
• Oder auch Fluent API
˃ Domain – [Domäne]
• Problemorientiert
• „Fachsprache“
˃ Specific – [Speziell]
• Konkret entworfen für einen spezifischen Einsatzzweck
• Abgrenzung zur General Purpose Language
Allgemeine Definition
6
Domäne
˃ Sammlung an Abläufen, Objekten und Rahmenbedingungen die Teil der Fachdomäne sind
˃ Brücke zwischen realen Objekten aus der Welt des Anwenders und Objekten eines Softwaresystems
˃ Domain-Driven-Design
• Schwerpunkt ist die Fachlichkeit und Fachlogik
• Ideales Einsatzgebiet für DSLs!
… oder auch Anwendungsdomäne
7
Ausdrucksschwäche von GPLs Ocean ocean = new Ocean ();
Fish fish1 = new Fish ();
fish1.setSize(Size.TINY);
fish1.setColor(Color.RED);
Fish fish2 = new Fish ();
fish2.setSize(Size.MIDSIZE);
fish2.setColor(Color.BLUE);
Shark shark = new Shark ();
shark.setSize(Size.HUGE);
shark.setColor(Color.WHITE);
Jellyfish jellyfish = new Jellyfish ();
jellyfish.setSize(Size.SMALL);
Turtle turtle = new Turtle ();
turtle.setSize (Size.SMALL);
ocean.add (fish1);
ocean.add (fish2);
ocean.add (shark);
ocean.add (jellyfish);
ocean.add (turtle);
8
Ausdrucksschwäche von GPLs Ocean ocean = new Ocean ();
Fish fish1 = new Fish ();
fish1.setSize(Size.TINY);
fish1.setColor(Color.RED);
Fish fish2 = new Fish ();
fish2.setSize(Size.MIDSIZE);
fish2.setColor(Color.BLUE);
Shark shark = new Shark ();
shark.setSize(Size.HUGE);
shark.setColor(Color.WHITE);
Jellyfish jellyfish = new Jellyfish ();
jellyfish.setSize(Size.SMALL);
Turtle turtle = new Turtle ();
turtle.setSize (Size.SMALL);
ocean.add (fish1);
ocean.add (fish2);
ocean.add (shark);
ocean.add (jellyfish);
ocean.add (turtle);
Wenn man eigentlich meint…
ocean (
fish (TINY, RED),
fish (SMALL, BLUE),
shark (HUGE, WHITE),
jellyfish (SMALL),
turtle (SMALL)
)
9
? ? ? ?
Ausdrucksschwäche von GPLs Ocean ocean = new Ocean ();
Fish fish1 = new Fish ();
fish1.setSize(Size.TINY);
fish1.setColor(Color.RED);
Fish fish2 = new Fish ();
fish2.setSize(Size.MIDSIZE);
fish2.setColor(Color.BLUE);
Shark shark = new Shark ();
shark.setSize(Size.HUGE);
shark.setColor(Color.WHITE);
Jellyfish jellyfish = new Jellyfish ();
jellyfish.setSize(Size.SMALL);
Turtle turtle = new Turtle ();
turtle.setSize (Size.SMALL);
ocean.add (fish1);
ocean.add (fish2);
ocean.add (shark);
ocean.add (jellyfish);
ocean.add (turtle);
Mit Scala 2.8: Named Parameters
ocean (
fish (size=TINY, color=RED),
jellyfish (size=SMALL)
)
10
? ? ? ?
Klassifizierung von DSLs
11
[5]
Klassifizierung
˃ Es gibt zwei wesentliche Unterschiede bei DSLs
• Interne DSLs
• Externe DSLs
˃ Es gibt allerdings auch einige graphische DSLs
12
Interne DSLs
˃ Eingebettet in eine Host-Sprache
• z. B. in Java, Groovy, Scala, Python, C# …
˃ Beeinflusst und limitiert von den Sprachmitteln der Host-Sprache
˃ Einige Vertreter
• LINQ (C#), Grails (Groovy), Lift (Scala), WebDSL (Scala)
13
Externe DSLs
˃ Liegen meist in Form von Skripten oder interpretierbaren Text-Dateien vor
• Graphviz, (X)HTML
˃ Werden entweder
• interpretiert
• compiliert in eine andere Sprache (Xtext)
˃ Kennen wir alle nur zu gut…
• SQL, Ant-Skripte, Makefiles, XML Konfigurationen
14
Graphische DSLs
˃ Graphische Modellierung einer Domäne
• UML, BPM
˃ Meist verbunden mit einem spezifischen Tool
• Bsp. Microsoft Oslo, JetBrains MPS, Intentional Domain Workbench
˃ Gut geeignet zum Dokumentieren
˃ Schlecht geeignet zum eigentlichen Entwickeln!
15
Stand der Dinge
16
[6]
Cream c = new WhippedCream ();
Coffee coffee = new
Coffee(”CinnamonDolce”,
TYPE_LATTE);
coffee.sized(4);
coffee.setDecaf
(”decaf none”);
coffee.addCream(c);
Naive API … ohne wirkliches Design
17
[7]
Query API
Coffee coffee = new Coffee();
coffee.setSize(Size.Grande);
coffee.setType(Type.CinnamonDolceLatte);
coffee.setDecaf(DecafLimit.Full);
coffee.setMilk(Milk.NonFat);
coffee.setCream(Cream.WhippedCream);
… am Starbucks Beispiel
18
Query API
Coffee coffee = new Coffee();
coffee.setSize(Size.Grande);
coffee.setType(Type.CinnamonDolceLatte);
coffee.setDecaf(DecafLimit.Full);
coffee.setMilk(Milk.NonFat);
coffee.setCream(Cream.WhippedCream);
… am Starbucks Beispiel
19
˃ Das geht doch schöner, oder?
Builder-Pattern
[8]
Das Builder-Pattern
˃ Grundlage • Mittels Method-Chaining Lesbarkeit von Query APIs
erhöhen
˃ Umsetzung • Ein oder mehrere Objekte die ein Fluent Interface
anbieten und in eine darunterliegende Query-API transformieren.
˃ Ziel • Soll ähnlich lesbar wie eine interne DSL sein
• Gutes Design für moderne APIs: Builder für die wichtigsten Objekte
… Expression-Builder
21
Object o = new Object.Builder(General)
.configureA (A)
.configureB (B)
.configureC (C)
.build ();
Allgemeine Umsetzung … In Java
22
[9]
Fluent-APIs (mit dem Builder-Pattern)
Coffee coffee = new Coffee.Builder
(Size.Grande, Type.CinnamonDolceLatte)
.with (DecafLimit.Half)
.with (Milk.Soy)
.with (Cream.WhippedCream)
.build();
… am Starbucks Beispiel
23
[10]
Fluent-APIs (mit dem Builder-Pattern)
Coffee coffee = new Coffee.Builder
(Size.Grande, Type.CinnamonDolceLatte)
.with (DecafLimit.Half)
.with (Milk.Soy)
.with (Cream.WhippedCream)
.build();
… am Starbucks Beispiel
24
˃ Nicht schlecht, aber warum soviel Overhead?
[10]
25
Code is written for people … and only then for computers!
[11]
Ziele für DSLs
26
[12]
Ziele
˃ Klare Wiedergabe der Absicht eines Systems
• Im Bezug auf die Domäne
˃ Einfachere Lesbarkeit
• flüssig
• Hin zu natürlicher Sprache
˃ Bessere Modellierung der Domäne
˃ Domänenexperten sollten DSL verstehen und lesen können
• Nicht zwingend selbst damit arbeiten (Wunschdenken)
Im Vordergrund
27
Ziele
˃ Abstraktionsebene von Software erhöhen
˃ Software-Entwicklung erleichtern
˃ Zeit einsparen
• Bei der Kommunikation mit Domänen-Experten
• Beim Umsetzen von fachlichen Anforderungen
… langfristig
28
Ziele
˃ Abstraktionsebene von Software erhöhen
˃ Software-Entwicklung erleichtern
˃ Zeit einsparen
• Bei der Kommunikation mit Domänen-Experten
• Beim Umsetzen von fachlichen Anforderungen
˃ Und nein, Entwickler wollen wir nicht abschaffen
• Hat bei COBOL auch nicht geklappt ;)
… langfristig
29
Schwierigkeiten
˃ Korrekte Abbildung einer Domäne in einer gegebenen Sprache oftmals schwierig
˃ Anpassung an die Host-Sprache notwendig
˃ DSL fühlt sich „fremd“ an in der Host-Sprache
˃ Warum passt sich die Sprache nicht der Domäne an?
… bei der Umsetzung
30
Roadmap für den Einsatz von DSLs … am Beispiel Scala
31
˃ Schritt 1 • Tests von Java Objekten mit Scala DSL
˃ Schritt 2 • Scala DSL als Smart-Wrapper
um Java Objekte herum
˃ Schritt 3 • Nicht-kritische Funktionalität mit Hilfe
einer Scala DSL modellieren
˃ Man muss nicht gleich Produktionscode auf Scala umstellen…
Warum Scala?
32
[13]
Warum Scala
˃ Weniger „Noise“
• Optionale Punkte beim Methodenaufruf
• Semikolon-Inferenz
• Typ-Inferenz
• Operatoren als Methoden und Infix-Operatoren
• Optionale Klammern (an manchen Stellen)
˃ Funktionale Aspekte
• Gleich mehr dazu
˃ Combinator Parsing (für externe DSLs)
… einsetzen für Domain Specific Languages?
33
„Fluent“-ness von Scala Ohne
34
val s : Int = new Palm().get (new
Banana(3)).size;
„Fluent“-ness von Scala mit Companion-Object und Type-Inference
35
val s : Int = new Palm().get (new
Banana(3)).size;
val s = Palm().get
(Banana(3)).size;
„Fluent“-ness von Scala mit Punkt- und Semicolon-Inferenz
36
val s : Int = new Palm().get (new
Banana(3)).size;
val s = Palm().get
(Banana(3)).size;
val s = Palm() get (Banana(3)) size
CLOSURES & FUNCTIONS
[14]
CLOSURES & FUNCTIONS
˃ Anonyme Funktionen
˃ Verwendung von Variablen aus dem aktuellen Scope möglich
Closures
val greeting = "Welcome"
val greet : String => String = s => {
greeting + " " + s
}
println greet("MSI Course") // Welcome MSI Course
38
CLOSURES & FUNCTIONS
Currying (1/2)
def uncurried(s:String, i:Int):String = {
s + i
}
println(uncurried("foo -> ", 1)) // foo -> 1
39
CLOSURES & FUNCTIONS
Currying (2/2)
def curried(s:String)(i:Int):String = {
s + " " + i
}
val prefill = curried("foo") _
val execute = prefill(1)
println(execute) // foo 1
val control = curried("foo"){
1 + 10 - 12
}
println(control) // foo -1
40
Currying ermöglicht - Partially Applied
Functions - „Neue“
Kontrollstrukturen
CLOSURES & FUNCTIONS
˃ „Magische Konvertierung“
˃ Vorhandene APIs erweitern
Implicit Conversions
41
class EnhancedInt(val int:Int){
def toBinaryString():String = {
// ...
}
}
implicit def intToEnhancedInt(i:Int):EnhancedInt={
new EnhancedInt(i)
}
println(12 toBinaryString) // 1100
Best Practice Implicit Conversions in Singleton Objects kapseln. import some.Object.*
CASE CLASSES
[15]
CASE CLASSES Idee
43
˃ Normale Klassen mit Modifier case
˃ Erhalten implizit erweiterte Funktionen
• Verwendbarkeit in Pattern Matching
• Implizites Companion Object
• Automatische Vergleichbarkeit
CASE CLASSES
Definition und Verwendung
44
abstract class TrainWaggon
case class StandardWaggon(seats : Int)
extends TrainWaggon
case class BistroWaggon(seats : Int, bar : Boolean)
extends TrainWaggon
case class BaggageWaggon(capacity : Int)
extends TrainWaggon
StandardWaggon(50)
BistroWaggon(20, true)
BaggageWaggon(200)
CASE CLASSES
Beispiel „Option“
45
val result : Option[String] = ... // Ergebnis einer
anderen Funktion
Some("Inhalt") // Es wurde ein Ergebnis erstellt und
zurückgeliefert
None // Es wurde kein Ergebnis erstellt
PATTERN MATCHING
[17]
PATTERN MATCHING Matching auf Werte
47
val input : String = "..." // Aus Eingaben, Dateien, ...
val choice = input match {
case "xml" => exportToXML
case "json" => exportToJSON
case "doc" => exportToWord
case "docx" if isDocxEnabled => exportToWord
case _ => invalidExport // Weitere Eingabenwerte
}
PATTERN MATCHING
˃ Case Classes optimal verwendbar
˃ Elegante Steuerung des Kontrollflusses
Matching mit Objekten
48
val input : Option[String] = ... // Some("test"), None
input match {
case Some("foobar") => false
case Some("test") => true
case None => false
}
PARTIAL FUNCTIONS
[18]
PARTIAL FUNCTIONS
50
val match1 : PartialFunction[String, String] = {
case "pictures" => displayPictureGallery
case "about" => displayAboutInformation
}
val match2 : PartialFunction[String, String] = {
case "users" => displayUserList
case "news" => displayNews
}
match1("users")
// scala.MatchError
match2("users")
// führt displayUserList aus
match1 isDefinedAt "users"
// false
match2 isDefinedAt "users„
// true
PARSER COMBINATORS
[19]
PARSER COMBINATORS
52
object SimpleScala extends RegexpParsers {
val ID = """[a-zA-Z]([a-zA-Z0-9]|_[a-zA-Z0-9])*"""r
val NUM = """[1-9][0-9]*"""r
def program = clazz*
// ...
def classPrefix = "class" ~ ID ~ "(" ~ formals ~ ")"
// ...
def expr: Parser[Expr] = factor ~ (
"+" ~ factor
| "-" ~ factor
)*
// ...
Parser direkt in Scala anhand einer DSL definieren. Beispiele: - JSON - SimpleScala - WebDSL
[MAGIC]
Beispiele für DSLs in Scala
[20]
Finance DSL in Scala
[21]
Finance DSL in Scala
val fixedIncomeTrade =
200.discount_bonds(IBM)
.for_client(NOMURA)
.on(NYSE)
.at(72.ccy(USD))
aus DSLsinAction
Beispiel aus [DSLSINACTION]
55
Finance DSL in Scala
val fixedIncomeTrade =
200 discount_bonds IBM
for_client NOMURA on NYSE at
72.ccy(USD)
aus DSLsinAction
Beispiel aus [DSLSINACTION]
56
Starbucks DSL in Scala
[22]
Starbucks Scala DSL
val o = order (
Tall (CinnamonDolceLatte decaf
None withMilk NonFat withCream
WhippedCream),
Grande (ConPanna decaf Half),
Venti (FlavoredLatte decaf None
withMilk Soy withCream
NoWhippedCream)
)
Beispiel für die Verwendung
58
Starbucks Scala DSL
val o = order (
Tall (CinnamonDolceLatte decaf
None withMilk NonFat withCream
WhippedCream),
Grande (ConPanna decaf Half),
Venti (FlavoredLatte decaf None
withMilk Soy withCream
NoWhippedCream)
)
Schade ist…
59
Starbucks Scala DSL
˃ Companion + apply () für order (…)
˃ Case Objects um „new“ zu entgehen
• Kaffeesorten (CinnamonDolceLatte, ConPanna…)
• Milchsorten (NonFat, Soy…)
• Größen (Tall, Venti, Grande…)
Verwendete Konzepte
60
ScalaTest DSL
[23]
ScalaTest DSL
class StackSpec extends FlatSpec with ShouldMatchers {
"A Stack" should "pop values in last-in-first-out order" in {
val stack = new Stack[Int]
stack.push(1)
stack.push(2)
stack.pop() should equal (2)
stack.pop() should equal (1)
}
it should "throw NoSuchElementException if an empty stack is popped" in {
val emptyStack = new Stack[String]
evaluating { emptyStack.pop() } should produce [NoSuchElementException]
}
}
http://www.scalatest.org
Beispiel von [SCALATEST]
62
Und mehr
˃ ScalaModules [SCALAMODULES]
• Konfiguration von OSGi Bundles
˃ Squeryl [SQUERYL]
• Scala ORM und DSL für SQL-Datenbanken
˃ Baysick [BAYSICK]
• Scala DSL die BASIC implementiert
˃ Apache Camel DSL [CAMELDSL]
• DSL zur Integration von ESB Komponenten
… weitere Scala DSLs
63
DB4O
[24]
[LOGOS]
DB4O
˃ Interfaces: Predicate<T>, Comparator<T>
Native Queries
65
final ObjectSet<User> nativeQuery = database.query(new
Predicate<User>() {
@Override
public boolean match(final User o) {
final User user = o;
return user.getLastName().equals("Flanders")
&& user.getFirstName().endsWith("od");
}
});
DB4O [SCADB4O] [SCADB4O2]
˃ Anonyme Klasse ersetzen durch Closure
˃ Transformation mit Implicit Conversions
Offensichtliche Vereinfachung für Native Queries
66
final ObjectSet<User> nativeQuery = database.query(new
Predicate<User>() { … });
db query { user : User => user.name.contains("t") }
implicit def toPredicate[T](predicate: T => Boolean) = {
new Predicate[T]() {
def `match`(entry: T): Boolean = {
predicate(entry)
}
}
}
Durch fehlende Kompatibilität mit Query Optimizer nicht „Production ready“
DB4O
˃ Builder-Pattern
SODA Queries
67
final Query query = database.query();
query.constrain(User.class);
query.descend("firstName").constrain("Johannes");
query.descend("lastName").constrain("Wachter");
query.descend("birthday").constrain("1985").like();
query.descend("age").constrain("19").smaller();
final ObjectSet<User> execute = query.execute();
DB4O
Idee
DB4O Abfragen SQL-ähnlich modellieren!
Komplexere DSL für SODA Queries (1/8)
68
DB4O Komplexere DSL für SODA Queries (2/8)
69
db select User.getClass where(„name := “Bart“) and('age < 20)
DB4O Komplexere DSL für SODA Queries (3/8)
70
db select User.getClass where(„name := “Bart“) and('age < 20)
ObjectContainer
implicit def oCToDSLOC(c : ObjectContainer):DSLObjectContainer =
DSLObjectContainer(c)
case class DSLObjectContainer(val c : ObjectContainer){…}
DB4O Komplexere DSL für SODA Queries (4/8)
71
db select User.getClass where(„name := “Bart“) and('age < 20)
case class DSLQuery[T](query : Query, clazz : Class[T])
extends QueryUtil{
def where(constr : DSLConstraint):ExtendedDSLQuery[T]={…}
def order(order : DSLOrdering):DSLQuery[T]{…}
def execute()={…}
}
DB4O Komplexere DSL für SODA Queries (5/8)
72
db select User.getClass where(„name := “Bart“) and('age < 20)
Symbol
implicit def sToConstr(s: Symbol):DSLConstraint={
DSLConstraint(symbol)
}
def :=(obj:Any):DSLConstraint={…}
DB4O Komplexere DSL für SODA Queries (6/8)
73
abstract class Operator
case object SMALLER extends Operator
case object SMALLER_EQUAL extends Operator
case class DSLConstraint(s : Symbol,
var b : Any = 0,
var op : Operator = EQUALS){
def :=(obj:Any):DSLConstraint={…}
def ~|(obj:String):DSLConstraint={…}
}
„name := “Bart“
DB4O Komplexere DSL für SODA Queries (7/8)
74
val res = c.operator match {
case EQUALS => q.descend(c.symbol.name)
.constrain(c.bound).equal
case SMALLER => q.descend(c.symbol.name)
.constrain(c.bound).smaller
case SMALLER_EQUAL =>
q.descend(c.symbol.name).constrain(c.bound).smaller()
.or(q.descend(c.symbol.name).constrain(c.bound).equal)
}
def or(c : DSLConstraint):ExtendedDSLQuery[T]={
q.constraints().or(constrain(q, c))
this
}
DB4O
˃ Verwendete Scala Features
• Implicit Conversions
• Builder Pattern
• Case Objects
• Pattern Matching
˃ Typisches Beispiel: Wrapper für Bibliothek
˃ Viele Möglichkeiten APIs „lesbar“ abzubilden
Komplexere DSL für SODA Queries (8/8)
75
THE DAWN OF DSLs
[25]
[LOGOS]
DEMO
77
[26]
SOURCECODE
http://github.com/jwachter/scala-db4o-dsl
NOCH FRAGEN?
79
[27]
Werbung
80
http://scala-southerngermany.mixxt.de/
Quellen
[BAYSICK] http://blog.fogus.me/2009/03/26/baysick-a-scala-dsl-implementing-basic/
[STIME] http://github.com/jorgeortiz85/scala-time
[SCADB4O] http://matlik.net/blog/2007/11/28/scala-and-db4o-native-queries/
[SCADB4O2] http://www.matthewtodd.info/?p=68
[SQUERYL] http://squeryl.org/ [SCALATEST] http://www.scalatest.org
[DSLSINACTION] Debashish Ghosh – DSLs In Action (MEAP) (http://manning.com/ghosh/)
[MAGIC] Daniel Spiewak, http://www.codecommit.com/blog/scala/the-magic-behind-parser-combinators
[FOWLER] Martin Fowler, http://martinfowler.com/dslwip (News zum Buch unter http://martinfowler.com/snips/201005261418.html) [SCALAMODULES] http://wiki.github.com/weiglewilczek/scalamodules/ [CAMELDSL] http://camel.apache.org/scala-dsl.html
81
Quellen
[1] XKCD, http://xkcd.com/353/ [2] http://www.flickr.com/photos/stephenccwu/3035854901/ [3] http://www.flickr.com/photos/travel_aficionado/2396814840/ [4] http://www.flickr.com/photos/leonardlow/340763653/ [5] http://www.flickr.com/photos/13698839@N00/3001363490/ [6] http://www.flickr.com/photos/spherical_perceptions/4634722058/ [7] http://www.flickr.com/photos/7202153@N03/4623364964/ [8] http://www.flickr.com/photos/v1ctory_1s_m1ne/3416173688/ [9] http://www.flickr.com/photos/gorbould/3083371867/ [10] http://www.flickr.com/photos/jpdaigle/4221047282/ [11] http://www.flickr.com/photos/trinity-of-one/20562069/ [12] http://www.flickr.com/photos/bogdansuditu/2377844553/ [13] http://www.flickr.com/photos/stuartmckenna/3104554689/ [14] http://www.flickr.com/photos/erikcharlton/421678891/
Bilder (1/3)
82
Quellen
[15] http://www.flickr.com/photos/zkorb/1592677291/
[17] http://www.flickr.com/photos/mortimer/221051561/
[18] http://www.flickr.com/photos/flying_cloud/2666399483/
[19] http://www.flickr.com/photos/fattytuna/8586848/
[20] selbst fotografiert (Buch: Programming in Scala)
[21] http://www.flickr.com/photos/travel_aficionado/2396824478/
[22] http://www.flickr.com/photos/webel/2406479887/
[23] http://www.flickr.com/photos/whisperwolf/3487084290/
[24] http://www.flickr.com/photos/adesigna/3237575990/
[25] http://www.flickr.com/photos/mugley/4207122005/
[26] http://www.flickr.com/photos/jof/263652571/
[27] http://www.flickr.com/photos/themonnie/2500388784/
Bilder (2/3)
83
Quellen
[LOGOS]
DB4O - http://www.db4o.com
LIFT - http://www.liftweb.net
WEBDSL - http://www.webdsl.org
Squeryl - http://max-l.github.com/Squeryl
ScalaTest - http://scalatest.org
Grails - http://www.grails.org
Scala - http://www.scala-lang.org
Groovy - http://groovy.codehaus.org
Ruby - http://ruby-lang.org
Python – http://www.python.org
Bilder(3/3)
84
License
Präsentation
85
http://creativecommons.org/licenses/by-nc-nd/3.0/de/
http://www.apache.org/licenses/LICENSE-2.0.html
Quelltext
top related