1. [1]
Scala
Scala
present DSLs in Scala for MSI at HSMA
Marcus Körner (@atla_) Johannes Wachter (@jow85)
2. 2
Grande
Cinnamon Dolce
Latte with tripple
shot with non-fat
milk topped with
whipped cream
[2]
3. 3
[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]
4. 4
Agenda
Was sind Warum
DSLs Scala?
DSL Beispiele
DB4O DSL
in Scala
6. 6
DSL
Allgemeine Definition
˃ 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
7. 7
Domäne
… oder auch Anwendungsdomä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!
8. 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);
9. 9
Ausdrucksschwäche von GPLs
Ocean ocean = new Ocean (); Wenn man eigentlich meint…
Fish fish1 = new Fish ();
ocean (
fish1.setSize(Size.TINY);
?
fish1.setColor(Color.RED);
Fish fish2 = new Fish ();
fish2.setSize(Size.MIDSIZE);
fish (TINY, RED),
fish (SMALL, BLUE),
shark (HUGE, WHITE),
?
fish2.setColor(Color.BLUE); jellyfish (SMALL),
Shark shark = new Shark (); turtle (SMALL)
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);
10. 10
Ausdrucksschwäche von GPLs
Ocean ocean = new Ocean (); Mit Scala 2.8: Named Parameters
Fish fish1 = new Fish ();
ocean (
fish1.setSize(Size.TINY);
?
fish1.setColor(Color.RED);
Fish fish2 = new Fish ();
fish2.setSize(Size.MIDSIZE); )
fish (size=TINY, color=RED),
jellyfish (size=SMALL)
?
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);
12. 12
Klassifizierung
˃ Es gibt zwei wesentliche Unterschiede bei DSLs
• Interne DSLs
• Externe DSLs
˃ Es gibt allerdings auch einige graphische DSLs
13. 13
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)
14. 14
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
15. 15
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!
17. 17
Naive API
… ohne wirkliches Design
Cream c = new WhippedCream ();
Coffee coffee = new [7]
Coffee(”CinnamonDolce”,
TYPE_LATTE);
coffee.sized(4);
coffee.setDecaf
(”decaf none”);
coffee.addCream(c);
18. 18
Query API
… am Starbucks Beispiel
Coffee coffee = new Coffee();
coffee.setSize(Size.Grande);
coffee.setType(Type.CinnamonDolceLatte);
coffee.setDecaf(DecafLimit.Full);
coffee.setMilk(Milk.NonFat);
coffee.setCream(Cream.WhippedCream);
19. 19
Query API
… am Starbucks Beispiel
Coffee coffee = new Coffee();
coffee.setSize(Size.Grande);
coffee.setType(Type.CinnamonDolceLatte);
coffee.setDecaf(DecafLimit.Full);
coffee.setMilk(Milk.NonFat);
coffee.setCream(Cream.WhippedCream);
˃ Das geht doch schöner, oder?
21. 21
Das Builder-Pattern
… Expression-Builder
˃ 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
22. 22
Allgemeine Umsetzung
… In Java
Object o = new Object.Builder(General)
.configureA (A)
.configureB (B)
.configureC (C)
.build ();
[9]
23. 23
Fluent-APIs (mit dem Builder-Pattern)
… am Starbucks Beispiel
Coffee coffee = new Coffee.Builder
(Size.Grande, Type.CinnamonDolceLatte) [10]
.with (DecafLimit.Half)
.with (Milk.Soy)
.with (Cream.WhippedCream)
.build();
24. 24
Fluent-APIs (mit dem Builder-Pattern)
… am Starbucks Beispiel
Coffee coffee = new Coffee.Builder
(Size.Grande, Type.CinnamonDolceLatte) [10]
.with (DecafLimit.Half)
.with (Milk.Soy)
.with (Cream.WhippedCream)
.build();
˃ Nicht schlecht, aber warum soviel
Overhead?
27. 27
Ziele
Im Vordergrund
˃ 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)
28. 28
Ziele
… langfristig
˃ Abstraktionsebene von Software erhöhen
˃ Software-Entwicklung erleichtern
˃ Zeit einsparen
• Bei der Kommunikation mit Domänen-Experten
• Beim Umsetzen von fachlichen Anforderungen
29. 29
Ziele
… langfristig
˃ 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 ;)
30. 30
Schwierigkeiten
… bei der Umsetzung
˃ 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?
31. 31
Roadmap für den Einsatz von DSLs
… am Beispiel Scala
˃ 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…
35. 35
„Fluent“-ness von Scala
mit Companion-Object und Type-Inference
val s : Int = new Palm().get (new
Banana(3)).size;
val s = Palm().get
(Banana(3)).size;
36. 36
„Fluent“-ness von Scala
mit Punkt- und Semicolon-Inferenz
val s : Int = new Palm().get (new
Banana(3)).size;
val s = Palm().get
(Banana(3)).size;
val s = Palm() get (Banana(3)) size
43. 43
CASE CLASSES
Idee
˃ Normale Klassen mit Modifier case
˃ Erhalten implizit erweiterte Funktionen
• Verwendbarkeit in Pattern Matching
• Implizites Companion Object
• Automatische Vergleichbarkeit
44. 44
CASE CLASSES
Definition und Verwendung
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)
45. 45
CASE CLASSES
Beispiel „Option“
val result : Option[String] = ... // Ergebnis einer
anderen Funktion
Some("Inhalt") // Es wurde ein Ergebnis erstellt und
zurückgeliefert
None // Es wurde kein Ergebnis erstellt
47. 47
PATTERN MATCHING
Matching auf Werte
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
}
48. 48
PATTERN MATCHING
Matching mit Objekten
˃ Case Classes optimal verwendbar
˃ Elegante Steuerung des Kontrollflusses
val input : Option[String] = ... // Some("test"), None
input match {
case Some("foobar") => false
case Some("test") => true
case None => false
}
55. 55
Finance DSL in Scala
aus DSLsinAction
val fixedIncomeTrade =
200.discount_bonds(IBM)
.for_client(NOMURA)
.on(NYSE)
.at(72.ccy(USD))
Beispiel aus [DSLSINACTION]
56. 56
Finance DSL in Scala
aus DSLsinAction
val fixedIncomeTrade =
200 discount_bonds IBM
for_client NOMURA on NYSE at
72.ccy(USD)
Beispiel aus [DSLSINACTION]
58. 58
Starbucks Scala DSL
Beispiel für die Verwendung
val o = order (
Tall (CinnamonDolceLatte decaf
None withMilk NonFat withCream
WhippedCream),
Grande (ConPanna decaf Half),
Venti (FlavoredLatte decaf None
withMilk Soy withCream
NoWhippedCream)
)
59. 59
Starbucks Scala DSL
Schade ist…
val o = order (
Tall (CinnamonDolceLatte decaf
None withMilk NonFat withCream
WhippedCream),
Grande (ConPanna decaf Half),
Venti (FlavoredLatte decaf None
withMilk Soy withCream
NoWhippedCream)
)
60. 60
Starbucks Scala DSL
Verwendete Konzepte
˃ Companion + apply () für order (…)
˃ Case Objects um „new“ zu entgehen
• Kaffeesorten (CinnamonDolceLatte, ConPanna…)
• Milchsorten (NonFat, Soy…)
• Größen (Tall, Venti, Grande…)
62. 62
ScalaTest DSL
http://www.scalatest.org
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]
}
}
Beispiel von [SCALATEST]
63. 63
Und mehr
… weitere Scala DSLs
˃ 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
69. 69
DB4O
Komplexere DSL für SODA Queries (2/8)
db select User.getClass where(„name := “Bart“) and('age < 20)
70. 70
DB4O
Komplexere DSL für SODA Queries (3/8)
ObjectContainer
db select User.getClass where(„name := “Bart“) and('age < 20)
implicit def oCToDSLOC(c : ObjectContainer):DSLObjectContainer =
DSLObjectContainer(c)
case class DSLObjectContainer(val c : ObjectContainer){…}
71. 71
DB4O
Komplexere DSL für SODA Queries (4/8)
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()={…}
}
72. 72
DB4O
Komplexere DSL für SODA Queries (5/8)
def :=(obj:Any):DSLConstraint={…}
Symbol
db select User.getClass where(„name := “Bart“) and('age < 20)
implicit def sToConstr(s: Symbol):DSLConstraint={
DSLConstraint(symbol)
}
73. 73
DB4O
Komplexere DSL für SODA Queries (6/8)
abstract class Operator
„name := “Bart“ 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={…}
}
74. 74
DB4O
Komplexere DSL für SODA Queries (7/8)
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
}
75. 75
DB4O
Komplexere DSL für SODA Queries (8/8)
˃ Verwendete Scala Features
• Implicit Conversions
• Builder Pattern
• Case Objects
• Pattern Matching
˃ Typisches Beispiel: Wrapper für Bibliothek
˃ Viele Möglichkeiten APIs „lesbar“ abzubilden