[1]

                                         Scala




                                                 Scala


present DSLs in Scala for MSI at HSMA
   Marcus Körner (@atla_)   Johannes Wachter (@jow85)
2



  Grande
  Cinnamon Dolce
  Latte with tripple
  shot with non-fat
  milk topped with
  whipped cream
[2]
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
Agenda


           Was sind       Warum
            DSLs          Scala?


         DSL Beispiele
                         DB4O DSL
           in Scala
[4]




Was sind DSLs?
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
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
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
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
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);
11



                           [5]




Klassifizierung von DSLs
12
Klassifizierung

˃ Es gibt zwei wesentliche Unterschiede bei DSLs
   • Interne DSLs
   • Externe DSLs


˃ Es gibt allerdings auch einige graphische DSLs
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
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
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!
16



                  [6]




Stand der Dinge
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
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
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?
[8]




Builder-Pattern
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
Allgemeine Umsetzung
… In Java

    Object o = new Object.Builder(General)
                       .configureA (A)
                       .configureB (B)
                       .configureC (C)
                       .build ();

                       [9]
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
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?
25

Code is written for people
    … and only then for computers!




                 [11]
26




                 [12]




Ziele für DSLs
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
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
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
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
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…
32




               [13]




Warum Scala?
33
Warum Scala
… einsetzen für Domain Specific Languages?

˃ 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)
34
„Fluent“-ness von Scala
Ohne
val s : Int = new Palm().get (new
Banana(3)).size;
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
„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
[14]




CLOSURES & FUNCTIONS
38
CLOSURES & FUNCTIONS
Closures

˃ Anonyme Funktionen
˃ Verwendung von Variablen aus dem aktuellen
  Scope möglich

     val greeting = "Welcome"

     val greet : String => String = s => {
        greeting + " " + s
     }

     println greet("MSI Course") // Welcome MSI Course
39
CLOSURES & FUNCTIONS
Currying (1/2)




     def uncurried(s:String, i:Int):String = {
          s + i
     }

     println(uncurried("foo -> ", 1)) // foo -> 1
40
CLOSURES & FUNCTIONS
Currying (2/2)

           def curried(s:String)(i:Int):String = {
                s + " " + i
           }


           val prefill = curried("foo") _      Currying ermöglicht

           val execute = prefill(1)            - Partially Applied
                                                 Functions
           println(execute) // foo 1           - „Neue“
                                                 Kontrollstrukturen

             val control = curried("foo"){
                   1 + 10 - 12
             }

             println(control) // foo -1
41
CLOSURES & FUNCTIONS
Implicit Conversions                        Best Practice

                                            Implicit Conversions in
˃ „Magische Konvertierung“                  Singleton Objects
                                            kapseln.
˃ Vorhandene APIs erweitern
                                            import some.Object.*
     class EnhancedInt(val int:Int){
          def toBinaryString():String = {
            // ...
          }
     }

     implicit def intToEnhancedInt(i:Int):EnhancedInt={
           new EnhancedInt(i)
     }

     println(12 toBinaryString) // 1100
[15]




CASE CLASSES
43
CASE CLASSES
Idee

˃ Normale Klassen mit Modifier case
˃ Erhalten implizit erweiterte Funktionen
   • Verwendbarkeit in Pattern Matching
   • Implizites Companion Object
   • Automatische Vergleichbarkeit
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
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
[17]




PATTERN MATCHING
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
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
  }
[18]




PARTIAL FUNCTIONS
50
PARTIAL FUNCTIONS

    val match1 : PartialFunction[String, String] = {
          case "pictures" => displayPictureGallery
          case "about" => displayAboutInformation
    }

    val match2 : PartialFunction[String, String] = {
          case "users" => displayUserList
          case "news" => displayNews
    }


match1("users")                match1 isDefinedAt "users"
// scala.MatchError            // false

match2("users")                match2 isDefinedAt "users„
// führt displayUserList aus   // true
[19]




PARSER COMBINATORS
52
PARSER COMBINATORS [MAGIC]
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 ~ ")"

// ...                                       Parser direkt in Scala
                                             anhand einer DSL
  def expr: Parser[Expr] = factor ~ (        definieren.
       "+" ~ factor
     | "-" ~ factor                          Beispiele:
  )*                                         - JSON
                                             - SimpleScala
// ...                                       - WebDSL
[20]




Beispiele für DSLs in Scala
[21]




Finance DSL in Scala
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
Finance DSL in Scala
aus DSLsinAction


 val fixedIncomeTrade =
             200 discount_bonds IBM
                         for_client NOMURA on NYSE at
                           72.ccy(USD)




Beispiel aus [DSLSINACTION]
[22]




Starbucks DSL in Scala
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
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
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…)
[23]




ScalaTest DSL
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
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
[24]




DB4O


       [LOGOS]
65
DB4O
Native Queries

˃ Interfaces: Predicate<T>, Comparator<T>

   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");
     }
  });
66
DB4O [SCADB4O] [SCADB4O2]
Offensichtliche Vereinfachung für Native Queries
  final ObjectSet<User> nativeQuery = database.query(new
  Predicate<User>() { … });

 ˃ Anonyme Klasse ersetzen durch Closure
 ˃ Transformation mit Implicit Conversions
  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“
67
DB4O
SODA Queries

˃ Builder-Pattern
   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();
68
DB4O
Komplexere DSL für SODA Queries (1/8)



                        Idee

    DB4O Abfragen SQL-ähnlich modellieren!
69
DB4O
Komplexere DSL für SODA Queries (2/8)




db select User.getClass where(„name := “Bart“) and('age < 20)
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
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
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
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
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
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
[25]




THE DAWN OF DSLs
                   [LOGOS]
77




       [26]




DEMO
SOURCECODE

http://github.com/jwachter/scala-db4o-dsl
79




               [27]




NOCH FRAGEN?
80
Werbung




     http://scala-southerngermany.mixxt.de/
81
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
82
Quellen
Bilder (1/3)
 [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/
83
Quellen
Bilder (2/3)
 [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/
84
Quellen
Bilder(3/3)
 [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
85
License
Präsentation




            http://creativecommons.org/licenses/by-nc-nd/3.0/de/


Quelltext



               http://www.apache.org/licenses/LICENSE-2.0.html

DSLs in Scala & DB4O

  • 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
  • 5.
  • 6.
    6 DSL Allgemeine Definition ˃ DomainSpecific 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 auchAnwendungsdomä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 Oceanocean = 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 Oceanocean = 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 Oceanocean = 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);
  • 11.
    11 [5] Klassifizierung von DSLs
  • 12.
    12 Klassifizierung ˃ Es gibtzwei wesentliche Unterschiede bei DSLs • Interne DSLs • Externe DSLs ˃ Es gibt allerdings auch einige graphische DSLs
  • 13.
    13 Interne DSLs ˃ Eingebettetin 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 ˃ Liegenmeist 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 ˃ GraphischeModellierung 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!
  • 16.
    16 [6] Stand der Dinge
  • 17.
    17 Naive API … ohnewirkliches 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 … amStarbucks 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 … amStarbucks 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?
  • 20.
  • 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 … InJava Object o = new Object.Builder(General) .configureA (A) .configureB (B) .configureC (C) .build (); [9]
  • 23.
    23 Fluent-APIs (mit demBuilder-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 demBuilder-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?
  • 25.
    25 Code is writtenfor people … and only then for computers! [11]
  • 26.
    26 [12] Ziele für DSLs
  • 27.
    27 Ziele Im Vordergrund ˃ KlareWiedergabe 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 derUmsetzung ˃ 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 denEinsatz 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…
  • 32.
    32 [13] Warum Scala?
  • 33.
    33 Warum Scala … einsetzenfür Domain Specific Languages? ˃ 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)
  • 34.
    34 „Fluent“-ness von Scala Ohne vals : Int = new Palm().get (new Banana(3)).size;
  • 35.
    35 „Fluent“-ness von Scala mitCompanion-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 mitPunkt- 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
  • 37.
  • 38.
    38 CLOSURES & FUNCTIONS Closures ˃Anonyme Funktionen ˃ Verwendung von Variablen aus dem aktuellen Scope möglich val greeting = "Welcome" val greet : String => String = s => { greeting + " " + s } println greet("MSI Course") // Welcome MSI Course
  • 39.
    39 CLOSURES & FUNCTIONS Currying(1/2) def uncurried(s:String, i:Int):String = { s + i } println(uncurried("foo -> ", 1)) // foo -> 1
  • 40.
    40 CLOSURES & FUNCTIONS Currying(2/2) def curried(s:String)(i:Int):String = { s + " " + i } val prefill = curried("foo") _ Currying ermöglicht val execute = prefill(1) - Partially Applied Functions println(execute) // foo 1 - „Neue“ Kontrollstrukturen val control = curried("foo"){ 1 + 10 - 12 } println(control) // foo -1
  • 41.
    41 CLOSURES & FUNCTIONS ImplicitConversions Best Practice Implicit Conversions in ˃ „Magische Konvertierung“ Singleton Objects kapseln. ˃ Vorhandene APIs erweitern import some.Object.* class EnhancedInt(val int:Int){ def toBinaryString():String = { // ... } } implicit def intToEnhancedInt(i:Int):EnhancedInt={ new EnhancedInt(i) } println(12 toBinaryString) // 1100
  • 42.
  • 43.
    43 CASE CLASSES Idee ˃ NormaleKlassen mit Modifier case ˃ Erhalten implizit erweiterte Funktionen • Verwendbarkeit in Pattern Matching • Implizites Companion Object • Automatische Vergleichbarkeit
  • 44.
    44 CASE CLASSES Definition undVerwendung 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
  • 46.
  • 47.
    47 PATTERN MATCHING Matching aufWerte 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 mitObjekten ˃ 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 }
  • 49.
  • 50.
    50 PARTIAL FUNCTIONS val match1 : PartialFunction[String, String] = { case "pictures" => displayPictureGallery case "about" => displayAboutInformation } val match2 : PartialFunction[String, String] = { case "users" => displayUserList case "news" => displayNews } match1("users") match1 isDefinedAt "users" // scala.MatchError // false match2("users") match2 isDefinedAt "users„ // führt displayUserList aus // true
  • 51.
  • 52.
    52 PARSER COMBINATORS [MAGIC] objectSimpleScala 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 ~ ")" // ... Parser direkt in Scala anhand einer DSL def expr: Parser[Expr] = factor ~ ( definieren. "+" ~ factor | "-" ~ factor Beispiele: )* - JSON - SimpleScala // ... - WebDSL
  • 53.
  • 54.
  • 55.
    55 Finance DSL inScala aus DSLsinAction val fixedIncomeTrade = 200.discount_bonds(IBM) .for_client(NOMURA) .on(NYSE) .at(72.ccy(USD)) Beispiel aus [DSLSINACTION]
  • 56.
    56 Finance DSL inScala aus DSLsinAction val fixedIncomeTrade = 200 discount_bonds IBM for_client NOMURA on NYSE at 72.ccy(USD) Beispiel aus [DSLSINACTION]
  • 57.
  • 58.
    58 Starbucks Scala DSL Beispielfü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 Schadeist… 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 VerwendeteKonzepte ˃ Companion + apply () für order (…) ˃ Case Objects um „new“ zu entgehen • Kaffeesorten (CinnamonDolceLatte, ConPanna…) • Milchsorten (NonFat, Soy…) • Größen (Tall, Venti, Grande…)
  • 61.
  • 62.
    62 ScalaTest DSL http://www.scalatest.org class StackSpecextends 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 … weitereScala 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
  • 64.
    [24] DB4O [LOGOS]
  • 65.
    65 DB4O Native Queries ˃ Interfaces:Predicate<T>, Comparator<T> 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"); } });
  • 66.
    66 DB4O [SCADB4O] [SCADB4O2] OffensichtlicheVereinfachung für Native Queries final ObjectSet<User> nativeQuery = database.query(new Predicate<User>() { … }); ˃ Anonyme Klasse ersetzen durch Closure ˃ Transformation mit Implicit Conversions 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“
  • 67.
    67 DB4O SODA Queries ˃ Builder-Pattern 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();
  • 68.
    68 DB4O Komplexere DSL fürSODA Queries (1/8) Idee DB4O Abfragen SQL-ähnlich modellieren!
  • 69.
    69 DB4O Komplexere DSL fürSODA Queries (2/8) db select User.getClass where(„name := “Bart“) and('age < 20)
  • 70.
    70 DB4O KomplexereDSL 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ürSODA 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ürSODA 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ürSODA 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ürSODA 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ürSODA 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
  • 76.
    [25] THE DAWN OFDSLs [LOGOS]
  • 77.
    77 [26] DEMO
  • 78.
  • 79.
    79 [27] NOCH FRAGEN?
  • 80.
    80 Werbung http://scala-southerngermany.mixxt.de/
  • 81.
    81 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
  • 82.
    82 Quellen Bilder (1/3) [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/
  • 83.
    83 Quellen Bilder (2/3) [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/
  • 84.
    84 Quellen Bilder(3/3) [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
  • 85.
    85 License Präsentation http://creativecommons.org/licenses/by-nc-nd/3.0/de/ Quelltext http://www.apache.org/licenses/LICENSE-2.0.html