Download as pdf or txt
Download as pdf or txt
You are on page 1of 41

XTEND – API AND DSL DESIGN PATTERNS

Max Bureck, 08. June 2016


XTEND – API AND DSL DESIGN PATTERNS

Intro – Xtend

− Xtend is a general purpose programming language transpiling to Java source

− Its syntax is flexible allowing definition of internal DSLs and interesting APIs

− This presentation will show some ways how the syntax can be utilized

− No detailed explanation of Xtend‘s features though

MatthiasHeyde
©Matthias
© FOKUS
FraunhoferFOKUS
Heyde//Fraunhofer
2
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Intro – Patterns

− Based on some observations from designing Xtend APIs

− Some ideas inspired by other languages (e.g. Scala, F#)

− Some patterns may or should be implemented via active annotations in future

MatthiasHeyde
©Matthias
© FOKUS
FraunhoferFOKUS
Heyde//Fraunhofer
3
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Intro – The Tools Provided By Xtend


− Lambdas
− Call with lambda as last parameter: place after brackets; omit empty brackets
strProv.apply([String s | println(s)]) ⇨ strProv.apply [println(it)]

− Setter call can be written as assignment

button.setText("Press Me") ⇨ button.text = "Press Me"

− Extension methods

emphasize("boo") ⇨ "boo".emphasize

− Operator overloading

FOKUS
FraunhoferFOKUS
operator_plus(1e15bd, 1e-4bd) ⇨ 1e15bd + 1e-4bd

Heyde//Fraunhofer
MatthiasHeyde
− Active annotations

©Matthias
©
4
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Pattern Overview

− Nested Block Syntax

− Fluent Case Distinction

− Immutable Data Structure Patterns

− Implicit Parameter Values

− Type Providers

− API Xtendification

5
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Nested Block Syntax, Use Case

− Lambda as last argument looks like a named block

− Can be exploited to create internal DSLs that look like nested blocks
− Declarative look, while being imperative

− Especially useful when building up object trees, e.g.


− UI elements
− Configuration
− Etc.

6
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Nested Block Syntax, Callback API Example in Java 8

server( (conf) -> {


conf.setPort(80);
conf.get("/hello?name=$name", (response) -> {
response.header(Pair.of("content", "text/html"));
return HtmlBuilder.html(response, (builder) -> {
builder.h1("Hello " + builder.param("name"));
});
});
});

7
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Nested Block Syntax, Callback API Example


Method with lambda argument
default argument it
server [ Assignment to setter on default argument
port = 80
Mapping operator
get("/hello?name=$name") [
header("Content-Type" -> "text/html")
html [ Extension method
h1("Hello " + param('name'))
]
]
]
Implicit return of last expression result

8
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Nested Block Syntax, Summary

− Nested block APIs reflect logical containment structures in code

− Xtend reduces visual noise and enables declarative look

− Can improve maintainability due to clear intent and readability of code

− "Traditional" APIs may be used as nested blocks, using => operator

9
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Fluent Case Distinction, Example: Object Decomposition in Java 8

ParentalStatus parentalStatus = bob.getParentalStatus();


if(parentalStatus instanceof Parents) {
Parents parents = (Parents) parentalStatus;
Optional<Person> momOpt = parents.getMom();
Optional<Person> dadOpt = parents.getDad();
momOpt.ifPresent((mom) -> dadOpt.ifPresent((dad) -> {
System.out.println("Mother: "+mom.getName()+", Father: "+ dad.getName());
}));
} else {
if(parentalStatus instanceof Orphan) {
String orphanage = ((Orphan) parentalStatus).getOrphanage();
System.out.println("Orphanage: "+orphanage);
} else {
System.out.println("Unknown parental status");
}
}

10
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Signal /

11
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Fluent Case Distinction, Example: Pattern Matching in Rust

match bob.parental_status {
Parents { mom: Some(ref mother), dad: Some(ref father) }
=> println!("Mother: {:?}, Father: {:?}", father.name, mother.name),
Orphan { orphanage: ref institute }
=> println!("Orphanage: {:?}", institute),
Unknown
=> println!("Parental status unknown"),
_ => {}
}

12
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Fluent Case Distinction, Pattern Matching: Short Description

− Comparable to switch statement in C like languages

− Matches a structural pattern of an object and it‘s fields

− Expression of first matching pattern will be executed

− Allows binding of field values to variable names (e.g. ref mother in example)

13
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Fluent Case Distinction – Intro

− Generic pattern matching with type-matching, decomposition and variable binding?

− Xtend switch expression “only” has instance check, no decomposition

− A library solution would be best

− But readable solution seems to be impossible without language support

− Next best thing are data type specific solutions

14
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Fluent Case Distinction, Example

val daredevil = Person::orphan("Matt Murdock", "St Agnes Orphanage")

daredevil.parentalStatus
.caseParents [ mom, dad |
println('''Mother: «mom.name», Father: «dad.name»''')
].caseOrphan [ orphanage |
println("Orphanage: " + orphanage)
].caseUnknown [
println("Unknown parental status")
]

15
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Fluent Case Distinction, Downsides

− Complex to implement, only makes sense if used multiple times

− No flexible nested decomposition and variable binding by caller

16
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Fluent Case Distinction, Summary / Use Cases

− Most times the powerful switch statement or multiple dispatch is good enough

− Still, this pattern can be useful for several use cases:


− Short notation for reoccurring, non trivial object decomposition

− Null-safe data access

− Can enforce exhaustive case handling or at least default case

− Alternative to inheritance hierarchies: No looking for all possible subclasses

17
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Fluent Case Distinction, Summary

− Fluent Case APIs can encapsulate reusable object decompositions

− They are an alternative to language-level pattern matching

− Come with implementation overhead

− Depending on usage (capturing in lambdas), may have runtime and memory overhead

18
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Immutable Data Structure Patterns – Intro

− Immutable objects are easier to reason about


− No unexpected changes when passed to methods

− Can safely be shared between threads

− Interestingly better for Java GC (according to Brian Goetz)

19
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Immutable Data Structure Patterns

− Immutable objects are tricky in some cases

What??? You said immutable!


− Especially demanding are:

− Object manipulation and

− Circular references Bear with me, explanation in


3 slides

20
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Immutable Data Structure Patterns: Object Instantiation

− Initialization using mutable builder objects

− Especially nice: Lambda builder pattern

− Example:
λ
val p = ImmutablePerson.create [
firstName = "Mack"
lastName = "The Knife"
]

21
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Immutable Data Structure Patterns: Lambda Builder Explained

− Static create method taking lambda


− Create method instantiates builder and calls lambda with it

− Lambda calls setters on builder

− Create method uses configured builder to create object and returns it

22
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Immutable Data Structure Patterns: Object Manipulation

− So called “persistent data structures“

− For simple structures: Fields may have ”update” method


− Takes lambda parameter mapping old field value to new value

− Returns new immutable updated object

23
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Immutable Object Patterns: Object Manipulation Example

class ImmutablePerson {
// ...
def ImmutablePerson firstName((String)=>String mapper) {
val newFirstName = mapper.apply(firstName)
new ImmutablePerson(newFirstName, lastName)
}
}

var p = ImmutablePerson.create […]


p = p.firstName["max"]
p = p.firstName[toFirstUpper]

24
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Immutable Data Structure Patterns: Object Manipulation - Problem

− Cyclic references will come back to bite you on manipulation!

− Especially when automatically generating manipulators

25
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Immutable Data Structure Patterns: Object Manipulation - Alternative

Manipulation by builder

− Create pre-filled builder from existing object

− Add some sugar for ease of use, similar to lambda builder

26
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Immutable Data Structure Patterns: Manipulation By Builder Example

var homer = ImmutablePerson.create [


firstName = "Homer"
lastName = "Simpson"
town = "Springfield"
]

homer = homer.with [
firstName = "Max"
lastName = "Power"
]

27
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Immutable Data Structure Patterns: Manipulation Specifics

− Always needs manipulation from "root" object to ensure consistency

− Pushing side effects to boundaries

− Advantage of “update” methods: Manipulation may be nested

− Cyclic references must be set lazy (same as in constructor)

28
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

API Xtendification – Intro

− Java APIs are not written with Xtend in mind

− Some language features of Xtend only shine when API is shaped in a certain way

− Extension methods to the rescue!

29
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

API Xtendification, Builder Extension Method; Example Before

Example calling constructor of type from JDK:

val queue = new LinkedBlockingDeque Whoops, wrong


val corePoolSize = 1 parameter order
val maximumPoolSize = 5
val keepAliveTime = 200
val keepAliveTimeUnit = TimeUnit.MILLISECONDS

val pool = new ThreadPoolExecutor(maximumPoolSize, corePoolSize,


keepAliveTime, keepAliveTimeUnit, queue)

30
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

API Xtendification, Builder Extension Method; Problem Statement

− When not using variables, long parameter lists are hard to understand

− When using variables


− It is possible to mess up parameter order

− Wrong variables might be used

− Code will compile, but might be wrong

− Solution: Use a builder class …

31
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

API Xtendification, Builder Extension Method; Example Definition

Let’s define an extension method:

static def ThreadPoolExecutor create(Class<ThreadPoolExecutor> clazz,


(ThreadPoolExecutorBuilder)=>void config) {
val builder = new ThreadPoolExecutorBuilder
config.apply(builder)
builder.build

32
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

API Xtendification, Builder Extension Method; Example Usage

Example using extension method

val pool = ThreadPoolExecutor.create [


λ
corePoolSize = 1
maximumPoolSize = 5
keepAliveTime = 200
keepAliveTimeUnit = TimeUnit.MILLISECONDS
workQueue = new LinkedBlockingDeque
]

33
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

API Xtendification, Builder Extension Method Explained

− Example shows alternative to overloaded constructors with different parameter lists

− It is supposed to look like a create method is called in class ThreadPoolExecutor

− Actual static method is located in different class

− Extension method call with class object of class ThreadPoolExecutor as first param

34
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Further Xtend Patterns

10 Java Idioms Stomped with Xtend


https://www.youtube.com/watch?v=n7LUgXX_3cE

35
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Summary

− Xtend language is pretty flexible, due to its syntax features

− Declarative looking internal DSLs are possible

− Enables new types of API patterns

− Patterns can be used to make Java APIs friendlier to use in Xtend

− Some patterns can be automated with Active Annotations

36
© Fraunhofer FOKUS
37
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Feedback and Opinions?

− Examples repository:
https://github.com/Boereck/eclipsecon_france_2016-xtend_patterns

− Useful?

− Interesting?

− Impractical?

− Too obvious?

− What are your favorite patterns?

38
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

Image Sources

− Max Power:
http://25.media.tumblr.com/tumblr_lxxowbwXTs1qhkm9yo1_400.gif

− Joda Pug:
https://unsplash.com/photos/2Ts5HnA67k8

39
© Fraunhofer FOKUS
XTEND – API AND DSL DESIGN PATTERNS

My Xtend Most Wanted Whish List

− Method references

− Default methods

− Compile auto-lambda-type-conversions to Java 8 method references, where possible

− Overloading call operator (also allow as extension method)


val nomnom = ["banana"]
nomnom()

− Pattern matching with decomposition

− More flexible active annotations

40
© Fraunhofer FOKUS
CONTACT

Fraunhofer FOKUS
Kaiserin-Augusta-Allee 31
10589 Berlin, Germany
www.fokus.fraunhofer.de

Max Bureck
Senior Researcher
max.bureck@fokus.fraunhofer.de
Phone +49 (0)30 3463-7321

41
© Fraunhofer FOKUS

You might also like