Upload
jaxconf
View
572
Download
0
Embed Size (px)
DESCRIPTION
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.
Citation preview
What You Need To Know About
@jamie_allen
Jamie Allen
•Director of Consulting
•User of Lambdas on the JVM for 4+ years
@jamie_allen
I Love Functional Programming!
•Functional Programming is:
•Immutable state
•Referential transparency
•Functions as first-‐class citizens
@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!
@jamie_allen
Java 8import 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()); }}
λ
@jamie_allen
Scala
object LambdaDemo extends App { val numbers = List(1, 2, 3) val numbersPlusOne = numbers.map(number => number + 1)}
λ
@jamie_allen
Clojure
(ns LambdaDemo.core)(defn -main [& args] (println(map #(+ % 1) [1, 2, 3])))
λ
@jamie_allen
JRuby
require "java"
array = [1, 2, 3]array.collect! do |n| n + 1end
λ
@jamie_allen
So What Is The Problem?
@jamie_allen
Not Reusable
•Lambdas are limited in scope to their call site
•You cannot reuse the functionality elsewhere
@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
@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
@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
@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
@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
@jamie_allen
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
@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
@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
@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)
@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
@jamie_allen
SOLUTION
•We want to maintain our ability to program in a functional style, while having something usable in production
@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
@jamie_allen
Named Function
object LambdaTest extends App { val addOneToValue = (x: Int) => x + 1 val myList = (1 to 20).map(addOneToValue(_)) println(myList)}
@jamie_allen
Stack Trace?val badFunction = (x: Int) => x / 0val 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
@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
@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?
@jamie_allen
Stack Trace of Methoddef badFunction = (x: Int) => x / 0val 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!
@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
@jamie_allen
Stack Trace of Methoddef badFunction(x: Int) = x / 0val 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!
@jamie_allen
Benefits•Can’t close over variables
•Internal variables are operands
•Better stack traces
•More debuggable
•More testable
•Easier maintenance
•Reusability
@jamie_allen
Rule of Thumb
•Keep lambda usage for the smallest expressions
•Externalize anything more than the most basic logic into methods
@jamie_allen
Language Creators
•Language designers need to help you with this!
•At Typesafe, we’re making our Scala IDE debugger more lambda-‐friendly with each version
@jamie_allen
Thank You!
•Questions?