3. I Love Functional Programming!
• Functional Programming is:
• Immutability
• Referential Transparency
• Functions as first-class citizens
4. We Want Declarative 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);
}
5. What is a Lambda?
• A function literal
• 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
6. 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());
}
}
λ
7. 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));
λ
13. Not Reusable
• Lambdas are limited in scope to their call
site
• You cannot reuse the functionality
elsewhere
14. 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
15. 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
16. 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)
}
} }}}
λλ
λ
λ
λ
17.
18. 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
19. 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
21. 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
22. 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
23. 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
24. 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
25. Difficult Debugging
• Debuggers on the JVM can only
disambiguate code at the source line -
write your lambdas to leverage this
final List<Integer> numbersPlusOne = numbers.stream().
map(number -> number + 1).collect(Collectors.toList());
NO!
26. 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());
27. 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)
28.
29. Solution
We want to maintain our ability to program
in a functional style, while having something
maintainable and understandable in
production
30. 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
31. Named Function
object LambdaTest extends App {
val addOneToValue = (x: Int) => x + 1
val myList = (1 to 20).map(addOneToValue)
}
32. Named Function 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
33. “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
34. Stack Trace of a 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)
Better, but
why $1
35. 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
36. 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!
37. Benefits
• You can’t close over variables
• Better stack traces
• More debuggable
• More testable
• More maintainable and descriptive
• Reusable
38. Rule of Thumb
• Reserve lambda usage for the most basic
expressions
• Externalize anything more significant than
that to methods