What You Need to Know about Lambdas - the problem with lambdas (as in anonymous functions) and the way to solve those problems (hint - using methods lifted to functions).
4. I Love Functional Programming!
• Functional Programming is:
• Immutability
• Referential Transparency
• Functions as first-class citizens
• Eliminating side effects
5. Why Functional Programming?
• Foundation for Reactive Programming
• Requires Callbacks to handle Events and
Async Results
• Avoid Inner Classes
• Higher-Level of Abstraction
• Define the What not the How
6. Imperative Code
final List<Integer> numbers =
Arrays.asList(1, 2, 3);
!
final List<Integer> numbersPlusOne =
Collections.emptyList();
!
for (Integer number : numbers) {
final Integer numberPlusOne = number + 1;
numbersPlusOne.add(numberPlusOne);
}
7. We Want Declarative Code
• Remove temporary lists - List<Integer>
numbersPlusOne = Collections.emptyList();
• Remove looping - for (Integer number : numbers)
• Focus on what - x+1
8. What is a Lambda?
• A function literal - fixed value in the code.
• Not bound to a variable name, can only be
used in the context of where it is defined
• Merely one of many possible
implementations you can use in Functional
Programming
9. Java 8 Lambdas
• Functional Interface that defines a Single
Abstract Method
• Target Type of the Lambda is the Functional
Interface
• java.util.function - Package with standard
Functional Interfaces
10. Java 8 Function Example
@FunctionalInterface
public interface Function<T, R> {
!
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
Target Type of Lambda
11. 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());
}
}
λ
12. Scala
• Have a Function Type used to represent the
Lambda
• map(f:Integer => Integer)
• Creates a Function underneath the covers
13. Scala
object LambdaDemo extends App {
val numbers = List(1, 2, 3)
val numbersPlusOne =
numbers.map(number => number + 1)
}
λ
14. Nashorn Javascript
#!/usr/bin/env jjs -scripting
!
var result = [];
var list = new java.util.ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.parallelStream().
map(function(e) e + 1).
forEach(function(t) result.push(t));
λ
19. Not Reusable
• Lambdas are limited in scope to their call
site
• You cannot reuse the functionality
elsewhere
20. Not Testable in Isolation
• How can you test code by itself when you
have no identifier through which you can
call it?
• You can only test them by writing more
tests for their enclosing method
21. Maintainability
• There is nothing inherently descriptive
about a lambda
• Developers have to read through the entire
lambda to figure out what it is doing
• The more complex the lambda is, the
harder this is to do
• Waste of valuable development time
22. Example
• In Scala, I sometimes see code like this:
val
next
=
x.map
{
case
Success(k)
=>
{
deriveValueAsynchronously(worker(initValue))(pec).map
{
case
None
=>
{
val
remainingWork
=
k(Input.EOF)
success(remainingWork)
None
}
case
Some(read)
=>
{
val
nextWork
=
k(Input.El(read))
Some(nextWork)
}
}(dec)
}
case
_
=>
{
success(it);
Future.successful(None)
}
}(dec)
}
} }}}
λλ
λ
λ
λ
23.
24. Lousy Stack Traces
• Compilers have to come up with generic
names for their representation of lambdas
to run on the JVM, called “name mangling”
• The stack trace output tells you very little
about where the problem occurred
25. Java 8
numbers.stream().map(number -> number / 0)
!
Exception in thread "main" java.lang.ArithmeticException: / by zero
at LambdaDemo.lambda$0(LambdaDemo.java:9)
at LambdaDemo$$Lambda$1.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:510)
at LambdaDemo.main(LambdaDemo.java:9)
wat
27. Favorite Tweet Ever
“JavaScript doesn't have a dark side, but it
does have a dimly lit room full of angry
clowns with rubber mallets.”
- @odetocode, Jan 5, 2010
28. 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
29. Clojureprintln(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
30. 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
31. Difficult Debugging
• Debuggers on the JVM are getting better -
still hard to debug Lambdas
final List<Integer> numbersPlusOne = numbers.stream().
map(number -> number + 1).collect(Collectors.toList());
NO!
32. Digression: Lambdas versus Closures
• In the purest sense, closures are merely
lambdas that close over some state from
outside of their enclosing scope
final int x = 1;
final List<Integer> numbersPlusOne =
numbers.stream().map(number -> number + x).
collect(Collectors.toList());
33. Closing Over State
• Lambdas have access to all variables that are in
scope
• It is very easy to “close over” something
mutable and cause headaches in multi-threaded
code
• Java enforces that values to be closed over are
final, but that only affects assignment - you can
still change what is INSIDE that variable (like
the contents of a collection)
34. Solution
We want to maintain our ability to program
in a functional style, while having something
maintainable and understandable in
production
35. 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 stack traces will still not
show the name of the function
36. Named Function
final Function<Integer, Integer> addOneToValue
= number -> number / 0;
!
final List<Integer> numbersPlusOne =
numbers.stream()
.map(addOneToValue)
.collect(Collectors.toList());
37. Named Function Stack Trace
final Function<Integer, Integer> addOneToValue
= number -> number / 0;
!
final List<Integer> numbersPlusOne =
numbers.stream().map(addOneToValue)
.collect(Collectors.toList());
!
!
Exception in thread "main" java.lang.ArithmeticException: / by zero
at demoFuncs.BadExamples.lambda$main$0(BadExamples.java:15)
at demoFuncs.BadExamples$$Lambda$1/1044036744.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at demoFuncs.BadExamples.main(BadExamples.java:20)
wat
38. “Lifting” a Method
• We have the ability in Java 8 and Scala to
“lift” or coerce a method into a function
• The method must meet the contract of the
lambda usage of the compiler, such as only
taking one argument representing the input
of the function
39. Stack Trace of a Method
Java 8
public static Integer addOneToValue(Integer number) {
return number / 0;
}
!
final List<Integer> numbersPlusOne =
numbers.stream()
.map(BadExamples::addOneToValue)
.collect(Collectors.toList());
!
Exception in thread "main" java.lang.ArithmeticException: / by zero
at demoFuncs.BadExamples.addOneToValue(BadExamples.java:12)
at demoFuncs.BadExamples$$Lambda$1/1826771953.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at demoFuncs.BadExamples.main(BadExamples.java:23)
Much
Better!
40. Stack Trace of a Method
Scala
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)
Better, but
why $1
41. Digression
• You can define your methods like that, but
“def” is not stable - it will reevaluate the
right side of the equals sign and return a
new but identical function each time!
def badFunction = (x: Int) => x / 0
def badFunction(x: Int) = x / 0
• Better to stick with simple method syntax instead
42. Stack Trace of a Stable 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) Perfect!
43. Benefits
• You can’t close over variables
• Better stack traces
• More debuggable
• More testable
• More maintainable and descriptive
• Reusable
44. Rule of Thumb
• Reserve lambda usage for the most basic
expressions
• Externalize anything more significant than
that to methods
45. Java 8 Futures
final CompletableFuture<Object> cacheFuture = CompletableFuture
.supplyAsync(() -> {
return cacheRetriever.getCustomer(id);
});
!
cacheFuture.thenApply(customer -> dbService.getOrders(customer))
• java.util.concurrent.CompletableFuture
• Future operation specified as a Function
• Callback specified as Function when the Future
completes
46. Java 8 vs. Scala
• Scala Functions are a first-class citizens
• Scala compiler can infer type of Function
• Java 8 Lambdas mapped to Functional
Interfaces
• Weaker Type Inference - Can only infer
type based on left hand side of assignment
operator
47. Java 8 vs. Scala
• Scala still targeting Java 1.6
• Creates synthetic class to represent Lambda
• Java 8 Lambdas leverage invoke dynamic
• Avoids creating synthetic classes
• http://www.takipiblog.com/2014/01/16/compiling-lambda-
expressions-scala-vs-java-8/
48. Language Creators
• Language designers and tool producers
need to help us
• At Typesafe, we’re making our Eclipse-based
Scala IDE more lambda-friendly with each
release - Smart stepping into Lambdas
• IntelliJ - Smart Step Into