Java 8
for functional programmers
BFPG May 2014
James Livingston
Me
- Former Java EE dev
- Currently senior Java tech support at Red Hat
- Background in Maths/Physics, touched FP briefly at Uni
- Seeing Scala ~5 years ago got me interested again
- Re-learnt Haskell and trying to practice more
- Given a few "FP idea in Java" talks before, to Java devs
Java 8
Lambdas
Streams
good bits
bad bits
the future
"Lambdas are relegated to relative obscurity until Java makes them popular by not having them."
-- James Iry
A Brief, Incomplete, and Mostly Wrong History of Programming Languages, 2009
Java 8 features and syntax
lambdas
Java 8 support lambda expressions
No new power, just more concise syntax
Java compiler converts lambdas to any "SAM" type:
Interface or class with a Single Abstract Method
Useful for dealing with old libraries
Lambda syntax
- Comma separated list of parameters
- in parentheses if not exactly one
- types optional
- Single arrow token ->
- Body
- expression with no "return"
- or statement block with braces and return
- or a void method call
(Integer a, Integer b) -> a + b
x -> x.toString()
i -> {int j = i + 1; return j}
s -> System.out.println(s)
Example
Old:
CollectionUtils.filter(list, new Predicate<String>() {
public boolean evaluate(String s) {
return s.startsWith("e");
}
});
New:
CollectionUtils.filter(list, s -> s.startsWith("e"));
CollectionUtils.filter(list, String::startsWith("e"));
Lambda variable access
- Only access effectively final variables
- Just like anonymous classes
- Can access (final )parameters and field too
Lambda typing
- Java lambdas use "target typing"
- Like sensible type inference but backwards
- just like the diamond operator <>
- Mostly works, except when it doesn't
- usually when you really want it
Lambda typing
Lambda expressions have no inherent type
Their type is determined by their target:
Runnable r = () -> { System.out.println("Hello World!"); };
No type, until something uses it:
() -> { System.out.println("Hello World!"); };
Method references
You can refer to existing methods
Class::method
instance::method
Class::new
There is no support for partial applicationDo it the long way
Class::staticMethod(1, _) // does not exist
_.something() // nor this
Can use them on fields or method callsCalled when creating the closure not inside it
list.forEach(System.out::println)
list.forEach(getOutputPrintStream()::println)
New types
There are interfaces to represent functions
java.util.function.Function<A,B> A -> B
java.util.function.BiFunction<A,B,C> (A,B) -> C
java.util.function.Consumer<A> A -> void
java.util.function.BiConsumer<A,B> (A,B) -> void
java.util.function.Supplier<A> void -> A
java.util.function.Predicate<A> A -> boolean
java.util.function.BiPredicate<A,B> (A,B) -> boolean
java.util.function.UnaryOperator<A> A -> A
java.util.function.BinaryOperator<A> (A,A) -> A
plus ones specialised for primitives
Some useful combinators, but not all you'd want
New types
java.util.Optional<A>
- (similar to Scala's Option or haskell Maybe)
- a "value-based class" to prepare for future
Useful replacement for null, but
- Lacks types like Functor and Monad (later)
- No do/for notation
Streams API
Java 8 has a new Collections-ish API using lambdas
Bridge from collections using default methods
Somewhat functional API
Streams - types
java.util.stream.Stream<A>
- Base class for streams
- Specialised versions - can't abstract across them
- No the other primitives
java.util.stream.Collector<T,R>
- Collates results
- An accumulate BiFunction<T,R> and combine BinaryOperator<R>, to handle parallelism.
creating streams
Two ways to create streams
From a collection:
List<String> words = ...
Stream<String> wordStream = words.stream();
From scratchStream<A> Streams.generator(Supplier<A>)
IntStream Streams.intRange(int start, int end)
Stream operations
Streams have two types of operations,
terminal and non-terminal
Non-terminal operations include
map, filter, flatMap etc.
Terminal operations include
collect/reduce(fold), forEach, anyMatch, max etc.
Streams utilities
java.util.stream.Streams class has utility methods
Stream<A> concat(Stream<A> a, Stream<A> b)
Stream<A> emptyStream()
Stream<A> iterate(A a, UnaryOperator<A> f)
Stream<C> zip(Stream<A> a, Stream<B> b, BiFunction<A,B,C> f)
and so on, with some (not all) primitive variantsstream operations
Most stream operations work as you'd expect
(unless you look to closely)
flatMap() aka bind has some issues
Many core lambda devs didn't want it
Type inference problems are annoying
FlatMapper interface which lets you use mutable Consumer<R>
Parallelism
stream() gives you a sequential stream
You can use parallelStream() or parallel()
Exactly how the parallelism is done is
an implementation detail
'Spliterator<T>' is like an Iterator<T>
which can be asked to split for parallelism
CompletableFuture<T>
Useful methods are not retrofitted onto Future<T>
They are on a new CompletableFuture<T>
Dealing with this is an exercise for the reader
<U> CompletionStage<U>thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn)
Wait, Scala's signatures are too complex?That isn't close the worst one
Good
Compiling to SAMs let you use lambdas with pre-8 APIs
Many library have APIs like this
Make Java code more concise and less tedious
Good - streams
Although there are some problems, Streams API isn't terrible
It should prevent an explosion of incompatible APIs
Good - Completable Future
Java finally has API for combining delayed computations!
It doesn't require you to manage your own thread pool
Bad - inconsistent API
Optional has ifPresent() not forEach()
Streams API has things that are logically Function but are not
Predicate<A> != Function<A, boolean>
UnaryOperator != Function<A,A> but is a subclass
Watch out for signature differences when passing around
Bad - partial application
s -> s.startsWith("e")
Wouldn't it be nice to have something like Scala? _.startsWith("e")
You can do it if there are no parameters
String::isEmpty()
or if all parameters are lambda parameters
(a,b) -> a.compareTo(b) === String::compareTo
but not if you need to specify any yourself
String::startsWith("e")
Bad - type inference
Type inference often fails with nested lambdas
Scala:
val af: Future<String> = ...; val bf: Future<String> = ...
val r: Future<String> = for (a <- af; b <- bf; res <- doit(a, b)) yield res
Java 8 (making up Range):Future<String> r = af.flatMap(a -> bf.flatMap(b -> doit(a,b))
Bzzt. javac thinks it returns Future<Object> not <String>
bad - no higher-kinded types
Java still doesn't have any higher-kinded types
From reading lambda-dev list, I doubt it ever will
No Monad<F> interface
Prevents some useful abstractions
Makes others incorrect
The future
Java 8 will introduce FP ideas to Java devs
Hopefully some will be interested in learning more
We need to encourage them - bring them to BFPG!
If you're "stuck" using Java, it will be better than it was
If you're using another JVM language (e.g. Scala), then
we may see some JVM-level improvements
References
http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
http://www.drdobbs.com/jvm/lambdas-and-streams-in-java-8-libraries/240166818
Java 8 for functional programmers
By doctau
Java 8 for functional programmers
- 973