The document discusses different approaches to controlling vehicles remotely using vocal commands in Java, including:
1. An object-oriented approach modeling commands as classes and executing them sequentially.
2. A pure functional approach using immutable functions to model commands and folding them to execute sequentially without side effects.
3. A functional approach with effects, using Try monads to isolate exceptions and allow sequential execution while handling errors.
2. HELLO!
I am Marian Wamsiedel
I work as a Java developer. You can find me at
marian.wamsiedel@gmail.com
2
3. Motivation
As always, the code examples are rather
naive, they are not meant to be used without
changes in a real life scenario.
Running code examples are available in a
github repository.
3
Functional Java
Java was designed as an object oriented programming
language, but, starting with version 8, it offers also
support for functional programming.
The choice between one of the two programming
paradigms is currently a discussed topic.
I present here an object oriented and some functional
solutions to implement a simple programming task.
4. The challenge:
Your company is building vehicles that need to be controlled remote by sending
some vocal commands.
The commands look like: “start”, “move forward”, “move left”, “stop” and so on.
Each vehicle implements an interface, that you design. The vehicle is being moved
by calling the interface methods, like: vehicle.moveForward(), or vehicle.stop().
A third-party library takes care of sending the vocal commands to a command
centre
The task is to write the code that runs the vocal commands on a vehicle.
The solution to be implemented
4
5. One problem, three solutions
5
Object
oriented
Pure
functional
Functional
with
effects
7. Object oriented approach
7
The Vehicle interface could contain methods like:
void wakeUp();
void moveForward(Distance distance);
void moveLeft(Distance distance);
void moveRight(Distance distance);
void stop();
Coordinate currentPosition();
Each method would throw a runtime exception if something goes wrong.
8. Object oriented approach
8
The vocal commands could be modelled as Command classes, each one exposing an
execute() method. We end up with mappings like these:
“move forward 6 meter” → new MoveForwardCommand(vehicle, Distance.meter(6))
“move left 3 meter” → new MoveLeftCommand(vehicle, Distance.meter(3))
“stop” → new StopCommand(vehicle)
A list of commands can be executed by iterating over it and calling the execute() method for
each of its items.
Some form of exception handling is expected, for instance: “logging the root cause and the
current vehicle position”.
9. Object oriented approach
9
Optimization: since the vehicle needs to be started before any move and stopped after the
ride completes, the application should take care automatically of start-stop moves. This can
be done using the template pattern:
Any ride consists of three types of actions: before raid (preparation), main ride and after ride
(clean up). A template superclass implements the before and after functionality and each
ride class provides the mainRide() method, that contains the specific moves.
Executable code examples in this repository:
https://github.com/mw1980/oofunctionalexample the package “oo”
10. Object oriented approach
10
The code for the vehicle ride would then look like:
new VehicleRide(
new MoveForwardCommand(vehicle, Distance.ONE_METER),
new MoveLeftCommand(vehicle, Distance.inch(2)),
new MoveRightCommand(vehicle, Distance.THREE_METER)
).startRide()
The VehicleRide class implements the main ride logic for the commands received as
parameter, while its parent, the AbstractTemplateRide class takes care of start – stop
operations.
https://github.com/mw1980/oofunctionalexample , the package “oo”
11. One problem, three solutions
11
Pure
functional
Object
oriented
Functional
with
effects
13. Pure functional approach
13
The functional programming has other goals, like referential transparency,
immutability and isolation of side effects.
Immutability is a central concept of the functional programming. Therefore the
methods that receive a parameter and return “void” look suspicious, because they
probably modify the state of their input parameter.
For instance, the move methods from the “oo” implementation would modify the
vehicle coordinates, fuel status, temperature and so on.
Therefore the Vehicle interface methods would rather return a new vehicle
instance, instead of void. For example:
Vehicle moveForward(Distance distance)
Vehicle stop()
14. Pure functional approach
14
The command classes from the object oriented implementation would be replaced
by some functions:
MOVE_FORWARD = distance-> vehicle -> vehicle.moveForward(distance)
MOVE_LEFT = distance-> vehicle -> vehicle.moveLeft(distance)
MOVE_RIGHT = distance-> vehicle -> vehicle.moveRight(distance)
STOP = Vehicle::stop
START = Vehicle::wakeUp
If the code looks weird, read more about currying here.
15. Pure functional approach
15
By fixing the distance in the above function definitions, the vehicle moves become functions
that depend only on the vehicle (unary operators):
UnaryOperator<Vehicle> moveLeft3 = MOVE_LEFT.apply(Distance.THREE_METER)
UnaryOperator<Vehicle> moveRight10 = MOVE_RIGHT.apply(Distance.TEN_METER)
UnaryOperator<Vehicle> moveForward13 = MOVE_FORWARD.apply(Distance.inch(13))
A simple ride would look like this:
START.andThen(moveLeft3).andThen(moveRight10).andThen(moveForward13)
.andThen(STOP).apply(vehicle)
16. Pure functional approach
16
The solution is very expressive, but still kind of verbose, requiring a new “andThen” call for
each move. Some functional patterns can make the code even compacter:
The monoid
The term comes from mathematics and it means a semi group with unity element.
To keep things simple, a monoid is a sequence of elements of some type: A, that support an
operation op : (A, A) → A.
There is also a special element of type A (the “unity”), that has no effect in the op
computation: op (unity, a) = op(a, unity) = a;
Read more about it here.
17. Pure functional approach
17
Examples:
- The strings, having concatenation as operation and the
empty string as unity element.
- The integer numbers, having the sum (the addition) as
operation and zero as unity element.
Since the functions are first class citizens in the
functional programming world, the A elements can be
functions, like the unary operators defined above and the
operation “op” can be the composition of functions:
18. Pure functional approach
18
a1 is “f1 : Vehicle → Vehicle”,
a2 is “f2 : Vehicle → Vehicle”,
op(a1, a2) is the composition of f1 and f2, also an unary operator for vehicles.
op(a1, a2) = a2 ◦ a1, or more detailed:
op(f1, f2) (vehicle) = f2.apply(f1.apply(vehicle))
The unity element would be the function that simply returns its input parameter:
unity(vehicle) = vehicle, for any vehicle object
19. Pure functional approach
19
Any monoid supports folding operations, by applying the
operation “op”:
- on unity element and on monoids first element to produce
a result
- then on the result and on monoids second element
- and so on, to end up with a single element.
Example: The folding of a sequence of integers, with
addition as operation and unity element zero would
produce a single value that is the sum of all integers in the
sequence.
20. Pure functional approach
20
Since the Java 8 Streams API implements already the folding functionality, the sequence of the
vehicle moves can be modelled also this way:
allMoves = Stream.of(moveLeft3, moveRight10, moveForward13)
.reduce(
UnaryOperator.identity(),
(first, second) -> v -> second.apply(first.apply(v))
)
allMoves.apply(vehicle)
https://github.com/mw1980/oofunctionalexample , package functional/pure
21. Pure functional approach
21
The prefixing and suffixing of some vehicle moves could be modelled as a function, that
replaces the template pattern. The ride function simply wraps a vehicle move between start
and stop actions.
ride: unaryVehicleOperator → start ◦ unaryVehicleOperator ◦ stop.
The code:
Function<UnaryOperator<Vehicle>, UnaryOperator<Vehicle>>
ride = f -> vehicle -> STOP.apply(f.apply(START.apply(vehicle)));
The code that moves the vehicle and returns its coordinate will look like:
ride.apply(allMoves).apply(vehicleInstance).currentPosition();
https://github.com/mw1980/oofunctionalexample , package functional/pure
22. One problem, three solutions
22
Functional
with
effects
Object
oriented
Pure
functional
24. Real world functional approach
24
We ignored the potential exceptions so far. They are considered in the functional programming
impure and therefore not supported. The exceptions are possible effects that hurt the
referential transparency of the system and should be isolated from the business logic
implementation.
Throwing and catching exceptions is seen as a glorified form of the “go to” statement, that
should be avoided.
The functional programming way to handle an exception is to wrap the suspect code block in
another component, like a Try, or an IO wrapper class.
The simple presence of Try, or IO components signalizes that the code might deal with side
effects, isolating the pure functional code from the rest.
25. Real world functional approach
▰ Lazy Try blocks
▰ Simple Try blocks
▰ Try with errors
25
26. Real world functional approach
26
In the context of vehicle operators, the try component could look like this:
class LazyTry<T> {
//injected in the constructor
private final UnaryOperator<T> f;
public T execute(final T t) {
try {f.apply(t);} // use try – catch only here
catch (RuntimeException exception) { // handle the error case }
}}
The Try class is a generic, lazy evaluated and reusable component.
27. Real world functional approach
27
The Try blocks can be composed, if they implement a
combine method, like “andThen”
LazyTry.to[f].andThen[g] = LazyTry.to[f ◦ g];
The operation can be repeated:
LazyTry.to[f1].andThen[f2] … andThen[fn] = LazyTry.to[f1 ◦
f2 ◦ … ◦ fn]
public LazyTry<T> andThen(UnaryOperator<T> after)
{
return
new LazyTry<>(t -> after.apply(f.apply(t)));
}
28. Real world functional approach
28
The vehicle moves could look like:
LazyTry.to(Vehicle::wakeUp)
.andThen(v -> v.moveLeft(FIVE_METER))
.andThen(v -> v.moveForward(THREE_METER))
...
.andThen(Vehicle::stop)
.onError(exception -> System.out.println(“err:”+ exception.getMessage()))
.execute(someVehicleInstance)
29. Real world functional approach
29
A sequence of lazy try blocks can be seen as a monoid, with:
- identity element = LazyTry.to( UnaryOperator.identity() )
- the lazyTryComposition(LazyTry.to[f], LazyTry.to[g]) = LazyTry.to[f◦g].
It is possible to fold on a sequence of try elements, using the Java 8 streams API. If the vehicle
moves are encapsulated in LazyTry instances, the code for the vehicle ride looks like:
Stream.of(LazyTry.of(firstVehicleMove), … , LazyTry.of(lastVehicleMove))
.reduce(identityElement, lazyTryComposition)
.execute(someVehicle);
The online repository contains an example:
https://github.com/mw1980/oofunctionalexample/ package functional/effects
30. Real world functional approach
▰ Lazy Try blocks
▰ Simple Try blocks
▰ Try with errors
30
31. Real world functional approach
31
The expressions that generate effects can also be initialized eagerly. The standard way is to
use a simple Try<T> block, that contains already the evaluated value of type T.
The Try class can have two subclasses: Success and Failure, that cover the successful
execution, or the operation failure.
The Try objects would be instantiated in a factory method:
static <T> Try<T> of(Supplier<T> supplier) {
try {
return new Success<>(supplier.get());
} catch (Exception exception) {
return new Failure<>(exception);
}
}
32. Real world functional approach
32
The Try<T> class could expose standard “map” and “flat map” methods, that lift some
operations on T type in the Try context:
public interface Try<T> {
...
Try<T> map(UnaryOperator<T> f);
Try<T> flatMap(Function<T, Try<T>> f);
…
}
33. Real world functional approach
33
The vehicle interface could look like:
public interface Vehicle {
Try<Vehicle> wakeUp();
Try<Vehicle> moveForward(Distance distance);
Try<Vehicle> moveLeft(Distance distance);
Try<Vehicle> moveRight(Distance distance);
Try<Vehicle> stop();
}
34. Real world functional approach
34
The vehicle moves can be modelled this way:
MOVE_FORWARD = distance -> vehicle -> vehicle.moveForward(distance);
MOVE_LEFT = distance -> vehicle -> vehicle.moveLeft(distance);
START = Vehicle::wakeUp;
STOP = Vehicle::stop;
Try.<Vehicle>of(Vehicle.Default::new)
.flatMap(START)
.flatMap(MOVE_FORWARD.apply(Distance.THREE_METER))
.flatMap(MOVE_LEFT.apply(Distance.FIVE_METER))
.flatMap(MOVE_RIGHT.apply(Distance.TEN_METER))
.flatMap(STOP);
The online repository contains an example.
https://github.com/mw1980/oofunctionalexample/ package “functional/effects/basic”
35. Real world functional approach
▰ Lazy Try blocks
▰ Simple Try blocks
▰ Try with errors
35
36. Real world functional approach
36
In some cases it is important to return the exception that might occur. The system could be
required to react differently, according to the exception type. The class Either can be used to
store also the potential error:
public class Either<T, V> {
//injected in the constructor, mutual exclusive
private final T left; //exception comes here, by convention
private final V right;
public static <T, V> Either<T, V> withLeftValue(final T value) {…}
public static <T, V> Either<T, V> withRightValue(final V value) {…}
}
37. Real world functional approach
37
The Try class could be rewritten to consider also exceptions:
public class TryWithError<T, U> {
private final Function<T, U> f;
public Either<RuntimeException, U> execute(T t) {
try { return Either.withRightValue(f.apply(t));}
catch (RuntimeException exception) {
return Either.withLeftValue(
new RuntimeException(“error message”, exception)
);
}}}
38. Real world functional approach
38
The code that performs the vehicle actions and returns also the exceptions that might occur
would then look like:
Either<RuntimeException, Vehicle> vehicleMoveResult =
TryWithError.to(Vehicle::wakeUp)
.andThen(v -> v.moveLeft(FIVE_METER))
...
.andThen(Vehicle::stop)
.execute(someVehicleInstance)
39. Real world functional approach
39
OO against FP, my two cents:
The functional programming:
+ extremely compact
+ handles the exceptions gracefully
+ truly reusable design patterns
- abstract
- requires more discipline
- it gets complex if it handles simultaneously more concerns, like handling exceptions and
missing return values
40. Real world functional approach
40
OO against FP, my two cents:
Object oriented programming:
+ easier to understand
+ easier to extend
+ its design patterns are a great communications instrument
+ it is trivial to write unit tests
+ the “Gof” design patterns, like Command and Template communicate well the code
intention. They are well known and generally accepted by the Java community
41. Real world functional approach
41
OO against FP, my two cents:
Object oriented programming:
- explosion of classes
- code verbosity
- difficult to maintain the overview when using template classes, especially if the class
inheritance hierarchy gets long
42. Read more
42
More documentation:
Functional and Reactive Domain Modeling (Debasish Gosh), Manning 2016
Functional Programming in Java (Pierre-Yves Saumont), Manning 2016
Vavr: http://www.vavr.io/
Eta lang: https://eta-lang.org/
Referential transparency: https://en.wikipedia.org/wiki/Referential_transparency
Currying:
https://hackernoon.com/functional-programming-paradigms-in-modern-javascript-currying-565
2e489cce8
Monoids: https://medium.com/@sjsyrek/five-minutes-to-monoid-fe6f364d0bba