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 application
Do it the long way
 Class::staticMethod(1, _) // does not exist
 _.something() // nor this
Can use them on fields or method calls
Called 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


java.util.stream.{Double,Int,Long}Stream
  • 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 scratch

Stream<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 variants

stream 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


Most effect-free ops are on CompletionStage<T>, e.g.
<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()


CompletableFuture has thenComposeAsync not flatMap


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

Made with Slides.com