Jamie Allen | Typesafe

What You Need to Know About Lambdas

Wednesday, November 6, 13
Who Am I?
• Director of Consulting for
• User of lambdas on the JVM for ~5 years
@jamie_allen

Wednesday, November 6, 13
Wednesday, November 6, 13
Wednesday, November 6, 13
Wednesday, November 6, 13
I Love Functional Programming!

• Functional Programming is:
• Immutability
• Referential Transparency
• Functions as first...
We Want Declarative Code
final List<Integer> numbers =
Arrays.asList(1, 2, 3);
final List<Integer> numbersPlusOne =
Collec...
What is a Lambda?
• A function literal
• Not bound to a variable name, can only be
used in the context of where it is defin...
Java 8
import java.util.List;
import java.util.Arrays;
import java.util.stream.Collectors;
public class LambdaDemo {
publi...
Nashorn Javascript
#!/usr/bin/env jjs -scripting
var result = [];
var list = new java.util.ArrayList();
list.add(1);
list....
Scala
λ
object LambdaDemo extends App {
val numbers = List(1, 2, 3)
val numbersPlusOne =
numbers.map(number => number + 1)...
Clojure
λ
(ns LambdaDemo.core)
(defn -main [& args]
(println(map #(+ % 1) [1, 2, 3])))

Wednesday, November 6, 13
JRuby
require "java"
array = [1, 2, 3]
array.collect! do |n|
n + 1
end

Wednesday, November 6, 13

λ
What is the Problem?

Wednesday, November 6, 13
There Are Caveats

Wednesday, November 6, 13
Not Reusable
• Lambdas are limited in scope to their call
site

• You cannot reuse the functionality
elsewhere

Wednesday,...
Not Testable in Isolation
• How can you test code by itself when you
have no identifier through which you can
call it?

• Y...
Maintainability
• There is nothing inherently descriptive
about a lambda

• Developers have to read through the entire
lam...
Example
• In Scala, I sometimes see code like this:
	
  	
  	
  	
  	
  	
  	
  	
  val	
  next	
  =	
  x.map	
  {
	
  	
 ...
Wednesday, November 6, 13
Lousy Stack Traces
• Compilers have to come up with generic

names for their representation of lambdas
to run on the JVM, ...
Java 8
numbers.stream().map(number -> number / 0)
Exception in thread "main" java.lang.ArithmeticException: / by zero
at L...
Nashorn Javascript
list.parallelStream().
map(function(e) e / 0)
[1, 2, 3]
Infinity,Infinity

Wednesday, November 6, 13
Nashorn Javascript
list.parallelStream().
map(function(e) e / 0)
[1, 2, 3]
Infinity,Infinity

Wednesday, November 6, 13
Favorite Tweet Ever
“JavaScript doesn't have a dark side, but
it does have a dimly lit room full of
angry clowns with rubb...
Scala
val numbersPlusOne = numbers.map(number => number / 0)
Exception in thread "main" java.lang.ArithmeticException: / b...
Clojure

println(map #(/ % 0) [1, 2, 3])))

Exception in thread "main" (java.lang.ArithmeticException: Divide by zero
at c...
JRuby
array.collect! do |n|
n / 0
ZeroDivisionError: divided by 0
/ at org/jruby/RubyFixnum.java:547
(root) at HelloWorld....
Difficult Debugging
• Debuggers on the JVM can only

disambiguate code at the source line write your lambdas to leverage th...
Digression: Lambdas versus Closures

• In the purest sense, closures are merely

lambdas that close over some state from
o...
Closing Over State

•

Lambdas have access to all variables that are in
scope

•

It is very easy to “close over” somethin...
Solution
We want to maintain our ability to program
in a functional style, while having something
maintainable and underst...
Named Functions?
• Seems like it would help, but it depends on
the compiler and how it manages the
“scope” of that functio...
Named Function
object LambdaTest extends App {
val addOneToValue = (x: Int) => x + 1
}

val myList = (1 to 20).map(addOneT...
Named Function Stack Trace
val badFunction = (x: Int) => x / 0
val myList = (1 to 20).map(badFunction)
Exception in thread...
“Lifting” a Method
• We have the ability in Java 8 and Scala to

“lift” or coerce a method into a function

• The method m...
Stack Trace of a Method
def badFunction = (x: Int) => x / 0
val myList = (1 to 20).map(badFunction)
Exception in thread "m...
Digression
• You can define your methods like that, but
“def” is not stable - it will reevaluate the
right side of the equa...
Stack Trace of a Stable Method
def badFunction(x: Int) = x / 0
val myList = (1 to 20).map(badFunction)
Exception in thread...
Benefits
• You can’t close over variables
• Better stack traces
• More debuggable
• More testable
• More maintainable and d...
Rule of Thumb
• Reserve lambda usage for the most basic
expressions

• Externalize anything more significant than
that to m...
Language Creators
• Language designers and tool producers
need to help us

• At Typesafe, we’re making our Eclipse-based
S...
Thank You!

• Questions?

Wednesday, November 6, 13
Upcoming SlideShare
Loading in …5
×

What You Need to Know About Lambdas - Jamie Allen (Typesafe)

1,016
-1

Published on

Presented as a keynote at JAX London 2013

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 have not yet learned the best ways to use them for maximum productivity. In this talk, we will discuss best practices for using Lambdas in Java and other JVM-based languages, and we will investigate how we can make these constructs more usable in production.

Published in: Technology, News & Politics
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,016
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
25
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

What You Need to Know About Lambdas - Jamie Allen (Typesafe)

  1. 1. Jamie Allen | Typesafe What You Need to Know About Lambdas Wednesday, November 6, 13
  2. 2. Who Am I? • Director of Consulting for • User of lambdas on the JVM for ~5 years @jamie_allen Wednesday, November 6, 13
  3. 3. Wednesday, November 6, 13
  4. 4. Wednesday, November 6, 13
  5. 5. Wednesday, November 6, 13
  6. 6. I Love Functional Programming! • Functional Programming is: • Immutability • Referential Transparency • Functions as first-class citizens Wednesday, November 6, 13
  7. 7. 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); } Wednesday, November 6, 13
  8. 8. 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 Wednesday, November 6, 13
  9. 9. 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); } } Wednesday, November 6, 13 λ final List<Integer> numbersPlusOne = numbers.stream().map(number -> number + 1). collect(Collectors.toList());
  10. 10. 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)); Wednesday, November 6, 13 λ
  11. 11. Scala λ object LambdaDemo extends App { val numbers = List(1, 2, 3) val numbersPlusOne = numbers.map(number => number + 1) } Wednesday, November 6, 13
  12. 12. Clojure λ (ns LambdaDemo.core) (defn -main [& args] (println(map #(+ % 1) [1, 2, 3]))) Wednesday, November 6, 13
  13. 13. JRuby require "java" array = [1, 2, 3] array.collect! do |n| n + 1 end Wednesday, November 6, 13 λ
  14. 14. What is the Problem? Wednesday, November 6, 13
  15. 15. There Are Caveats Wednesday, November 6, 13
  16. 16. Not Reusable • Lambdas are limited in scope to their call site • You cannot reuse the functionality elsewhere Wednesday, November 6, 13
  17. 17. 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 Wednesday, November 6, 13
  18. 18. 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 Wednesday, November 6, 13
  19. 19. 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) Wednesday, November 6, 13 }} }λ }λ } λ λ λ
  20. 20. Wednesday, November 6, 13
  21. 21. 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 Wednesday, November 6, 13
  22. 22. 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 Wednesday, November 6, 13
  23. 23. Nashorn Javascript list.parallelStream(). map(function(e) e / 0) [1, 2, 3] Infinity,Infinity Wednesday, November 6, 13
  24. 24. Nashorn Javascript list.parallelStream(). map(function(e) e / 0) [1, 2, 3] Infinity,Infinity Wednesday, November 6, 13
  25. 25. 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 Wednesday, November 6, 13
  26. 26. 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 Wednesday, November 6, 13
  27. 27. 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 Wednesday, November 6, 13
  28. 28. 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 Wednesday, November 6, 13
  29. 29. 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! Wednesday, November 6, 13
  30. 30. 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()); Wednesday, November 6, 13
  31. 31. 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) Wednesday, November 6, 13
  32. 32. Solution We want to maintain our ability to program in a functional style, while having something maintainable and understandable in production Wednesday, November 6, 13
  33. 33. 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 Wednesday, November 6, 13
  34. 34. Named Function object LambdaTest extends App { val addOneToValue = (x: Int) => x + 1 } val myList = (1 to 20).map(addOneToValue) Wednesday, November 6, 13
  35. 35. 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 Wednesday, November 6, 13
  36. 36. “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 Wednesday, November 6, 13
  37. 37. 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 Wednesday, November 6, 13
  38. 38. 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 • Better to stick with simple method syntax instead def badFunction(x: Int) = x / 0 Wednesday, November 6, 13
  39. 39. 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! Wednesday, November 6, 13
  40. 40. Benefits • You can’t close over variables • Better stack traces • More debuggable • More testable • More maintainable and descriptive • Reusable Wednesday, November 6, 13
  41. 41. Rule of Thumb • Reserve lambda usage for the most basic expressions • Externalize anything more significant than that to methods Wednesday, November 6, 13
  42. 42. 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 Wednesday, November 6, 13
  43. 43. Thank You! • Questions? Wednesday, November 6, 13
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×