Lambdas are coming to the Java language in the upcoming release of Java 8! While this is generally great news, many Java developers have never experienced lambdas before, and may not realize the costs and pitfalls associated with this style of programming. In this talk, we will discuss the many issues of using Lambdas in Java as well as Scala, and measures we can talk to avoid them. We will also review concepts associated with lambdas to make sure everyone is on the same page, such as closures, higher order functions and Eta Expansion.
3. @jamie_allen
I
Love
Functional
Programming!
•Functional
Programming
is:
•Immutable
state
•Referential
transparency
•Functions
as
first-‐class
citizens
4. @jamie_allen
What
is
a
Lambda?
•A
Function
Literal
•A
function
that
is
not
bound
to
a
name,
to
be
used
only
within
the
context
of
where
it
is
defined
•Merely
an
implementation
detail
of
Functional
Programming!
5. @jamie_allen
Java
8
import java.util.List;
import java.util.Arrays;
import java.util.stream.Collectors;
public class LambdaDemo {
public static void main(String... args) {
final List<Integer> numbers =
Arrays.asList(1, 2, 3);
final List<Integer> numbersPlusOne =
numbers.stream().map(number -> number + 1).
collect(Collectors.toList());
}
}
λ
11. @jamie_allen
Not
Testable
in
Isolation
•How
do
you
test
code
that
does
not
have
a
name
through
which
you
can
call
it?
•You
can
only
test
lambdas
in
the
context
of
their
call
site
12. @jamie_allen
Maintainability
•Developers
have
to
read
through
the
lambda
to
figure
out
what
it’s
trying
to
do
•The
more
complex
the
lambda,
the
harder
this
is
to
do
•Waste
of
valuable
development
time
13. @jamie_allen
Lousy
Stack
Traces
•Compilers
have
to
come
up
with
generic
names
for
the
classes
that
represent
the
lambdas
on
the
JVM,
called
“name
mangling”
•The
stack
trace
output
tells
you
very
little
about
where
the
problem
occurred
14. @jamie_allen
Java
8
final List<Integer> numbersPlusOne =
numbers.stream().map(number -> number / 0).
collect(Collectors.toList());
Exception in thread "main" java.lang.ArithmeticException: / by zero
at LambdaDemo.lambda$0(LambdaDemo.java:1)
at LambdaDemo$$Lambda$1.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:188)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:467)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:457)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:710)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:231)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:474)
at LambdaDemo.main(LambdaDemo.java:9)
wat
15. @jamie_allen
Scala
val numbersPlusOne = numbers.map(number => number / 0)
Exception in thread "main" java.lang.ArithmeticException: / by zero
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$$anonfun$1.apply(LambdaPlayground.scala:23)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$$anonfun$1.apply(LambdaPlayground.scala:23)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.immutable.Range.foreach(Range.scala:141)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
at scala.collection.AbstractTraversable.map(Traversable.scala:105)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$delayedInit$body.apply(LambdaPlayground.scala:23)
at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App$$anonfun$main$1.apply(App.scala:71)
at scala.App$$anonfun$main$1.apply(App.scala:71)
at scala.collection.immutable.List.foreach(List.scala:318)
at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:32)
at scala.App$class.main(App.scala:71)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$.main(LambdaPlayground.scala:22)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest.main(LambdaPlayground.scala)
wat
16. @jamie_allen
Clojure
println(map #(/ % 0) [1, 2, 3])))
Exception in thread "main" (java.lang.ArithmeticException: Divide by zero
at clojure.lang.Numbers.divide(Numbers.java:156)
at clojure.lang.Numbers.divide(Numbers.java:3671)
at helloclj.core$_main$fn__10.invoke(core.clj:5)
at clojure.core$map$fn__4087.invoke(core.clj:2432)
at clojure.lang.LazySeq.sval(LazySeq.java:42)
at clojure.lang.LazySeq.seq(LazySeq.java:60)
at clojure.lang.RT.seq(RT.java:473)
at clojure.core$seq.invoke(core.clj:133)
at clojure.core$print_sequential.invoke(core_print.clj:46)
at clojure.core$fn__5270.invoke(core_print.clj:140)
at clojure.lang.MultiFn.invoke(MultiFn.java:167)
at clojure.core$pr_on.invoke(core.clj:3266)
at clojure.core$pr.invoke(core.clj:3278)
at clojure.lang.AFn.applyToHelper(AFn.java:161)
at clojure.lang.RestFn.applyTo(RestFn.java:132)
at clojure.core$apply.invoke(core.clj:601)
at clojure.core$prn.doInvoke(core.clj:3311)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invoke(core.clj:601)
at clojure.core$println.doInvoke(core.clj:3331)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at helloclj.core$_main.invoke(core.clj:5)
at clojure.lang.Var.invoke(Var.java:411)
...
at clojure.main.main(main.java:37)
wat
17. @jamie_allen
JRuby
array.collect! do |n|
n / 0
ZeroDivisionError: divided by 0
/ at org/jruby/RubyFixnum.java:547
(root) at HelloWorld.rb:11
collect! at org/jruby/RubyArray.java:2385
(root) at HelloWorld.rb:10
not half bad, really
18. @jamie_allen
Difficult
Debugging
•Debuggers
on
the
JVM
can
only
disambiguate
code
at
the
source
line,
so
you
need
to
make
your
lambdas
multi-‐line
to
be
able
to
step
through
them
•Some
languages
allow
lambdas
to
use
“placeholders”
instead
of
names,
which
cannot
currently
be
“watched”
in
a
debugger
19. @jamie_allen
Digression:
Lambdas
vs
Closures
•Closures
are
merely
lambdas
that
“close
over”
state
available
to
them
from
their
enclosing
scope
val x = 1
(1 to 20).map(num => num + x)
20. @jamie_allen
Closing
Over
State
•Lambdas
have
access
to
all
state
within
their
enclosing
scope
•It
is
very
easy
to
manipulate
these
values
and
change
something
important
•If
the
lambda
is
in
use
for
a
Future
operation,
this
can
lead
to
race
conditions
21. @jamie_allen
SOLUTION
•We
want
to
maintain
our
ability
to
program
in
a
functional
style,
while
having
something
usable
in
production
22. @jamie_allen
Named
Functions?
•Seems
like
it
would
help,
but
it
depends
on
the
compiler
and
how
it
manages
the
“scope”
of
that
function
•It
is
possible
that
the
stack
trace
will
still
not
show
you
the
name
of
the
function
you
used
24. @jamie_allen
Stack
Trace?
val badFunction = (x: Int) => x / 0
val myList = (1 to 20).map(badFunction(_))
Exception in thread "main" java.lang.ArithmeticException: / by zero
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$$anonfun$1.apply$mcII$sp(LambdaPlayground.scala:23)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$$anonfun$2.apply(LambdaPlayground.scala:24)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$$anonfun$2.apply(LambdaPlayground.scala:24)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.immutable.Range.foreach(Range.scala:141)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
at scala.collection.AbstractTraversable.map(Traversable.scala:105)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$delayedInit$body.apply(LambdaPlayground.scala:24)
at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App$$anonfun$main$1.apply(App.scala:71)
at scala.App$$anonfun$main$1.apply(App.scala:71)
at scala.collection.immutable.List.foreach(List.scala:318)
at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:32)
at scala.App$class.main(App.scala:71)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$.main(LambdaPlayground.scala:22)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest.main(LambdaPlayground.scala)
wat
25. @jamie_allen
Eta
Expansion
•“Lifting”,
or
coercing,
a
method
to
be
used
as
a
function
•Must
meet
the
contract
of
the
lambda
usage
defined
by
the
compiler
in
use,
such
as
one
argument
for
the
value
to
be
manipulated
26. @jamie_allen
Methods
as
Functions
•In
Scala,
if
we
use
a
method,
it
can
be
“lifted”
into
a
function
and
give
us
everything
we
need
•But
what
syntax
should
we
use?
27. @jamie_allen
Stack
Trace
of
Method
def badFunction = (x: Int) => x / 0
val myList = (1 to 20).map(badFunction(_))
Exception in thread "main" java.lang.ArithmeticException: / by zero
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$$anonfun$badFunction$1.apply$mcII$sp(LambdaPlayground.scala:23)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$$anonfun$1.apply(LambdaPlayground.scala:24)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$$anonfun$1.apply(LambdaPlayground.scala:24)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.immutable.Range.foreach(Range.scala:141)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
at scala.collection.AbstractTraversable.map(Traversable.scala:105)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$delayedInit$body.apply(LambdaPlayground.scala:24)
at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App$$anonfun$main$1.apply(App.scala:71)
at scala.App$$anonfun$main$1.apply(App.scala:71)
at scala.collection.immutable.List.foreach(List.scala:318)
at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:32)
at scala.App$class.main(App.scala:71)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$.main(LambdaPlayground.scala:22)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest.main(LambdaPlayground.scala)
Yay!
28. @jamie_allen
Digression
•You
can
define
your
function
like
this,
but
“def”
is
not
stable
-‐
it
will
re-‐evaluate
the
right
side
of
the
equals
sign
and
return
a
new
but
identical
function
each
time!
def badFunction = (x: Int) => x / 0
•Better
to
stick
with
simple
method
syntax
def badFunction(x: Int) = x / 0
29. @jamie_allen
Stack
Trace
of
Method
def badFunction(x: Int) = x / 0
val myList = (1 to 20).map(badFunction(_))
Exception in thread "main" java.lang.ArithmeticException: / by zero
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$.badFunction(LambdaPlayground.scala:24)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$$anonfun$1.apply(LambdaPlayground.scala:25)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$$anonfun$1.apply(LambdaPlayground.scala:25)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.immutable.Range.foreach(Range.scala:141)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
at scala.collection.AbstractTraversable.map(Traversable.scala:105)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$delayedInit$body.apply(LambdaPlayground.scala:25)
at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App$$anonfun$main$1.apply(App.scala:71)
at scala.App$$anonfun$main$1.apply(App.scala:71)
at scala.collection.immutable.List.foreach(List.scala:318)
at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:32)
at scala.App$class.main(App.scala:71)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest$.main(LambdaPlayground.scala:22)
at org.jamieallen.effectiveakka.pattern.extra.LambdaTest.main(LambdaPlayground.scala)
Even better!
30. @jamie_allen
Benefits
•Can’t
close
over
variables
•Internal
variables
are
operands
•Better
stack
traces
•More
debuggable
•More
testable
•Easier
maintenance
•Reusability
31. @jamie_allen
Rule
of
Thumb
•Keep
lambda
usage
for
the
smallest
expressions
•Externalize
anything
more
than
the
most
basic
logic
into
methods