O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

What You Need to Know about Lambdas

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).

  • Seja o primeiro a comentar

  • Seja a primeira pessoa a gostar disto

What You Need to Know about Lambdas

  1. 1. @knight_cloud What You Need to Know About Lambdas Ryan Knight Senior Consultant | Typesafe
  2. 2. Acknowledgement • Material originally created by Jamie Allen • @jamie_allen • Modified by Ryan Knight • @knight_cloud
  3. 3. I Love Functional Programming! • Functional Programming is: • Immutability • Referential Transparency • Functions as first-class citizens • Eliminating side effects
  4. 4. 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
  5. 5. 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); }
  6. 6. We Want Declarative Code • Remove temporary lists - List<Integer> numbersPlusOne = Collections.emptyList(); • Remove looping - for (Integer number : numbers) • Focus on what - x+1
  7. 7. 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
  8. 8. 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
  9. 9. 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
  10. 10. 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. 11. Scala • Have a Function Type used to represent the Lambda • map(f:Integer => Integer) • Creates a Function underneath the covers
  12. 12. Scala object LambdaDemo extends App { val numbers = List(1, 2, 3) val numbersPlusOne = numbers.map(number => number + 1) } λ
  13. 13. 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)); λ
  14. 14. Clojure (ns LambdaDemo.core) (defn -main [& args] (println(map #(+ % 1) [1, 2, 3]))) λ
  15. 15. JRuby require "java" ! array = [1, 2, 3] array.collect! do |n| n + 1 end λ
  16. 16. What is the Problem?
  17. 17. There Are Caveats
  18. 18. Not Reusable • Lambdas are limited in scope to their call site • You cannot reuse the functionality elsewhere
  19. 19. 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
  20. 20. 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
  21. 21. 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)   } } }}} λλ λ λ λ
  22. 22. 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
  23. 23. 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
  24. 24. Nashorn Javascript list.parallelStream(). map(function(e) e / 0) ! [1, 2, 3] Infinity,Infinity
  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
  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
  27. 27. 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
  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
  29. 29. 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!
  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());
  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)
  32. 32. Solution We want to maintain our ability to program in a functional style, while having something maintainable and understandable in production
  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
  34. 34. Named Function final Function<Integer, Integer> addOneToValue = number -> number / 0; ! final List<Integer> numbersPlusOne = numbers.stream() .map(addOneToValue) .collect(Collectors.toList());
  35. 35. 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
  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
  37. 37. 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!
  38. 38. 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
  39. 39. 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
  40. 40. 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!
  41. 41. Benefits • You can’t close over variables • Better stack traces • More debuggable • More testable • More maintainable and descriptive • Reusable
  42. 42. Rule of Thumb • Reserve lambda usage for the most basic expressions • Externalize anything more significant than that to methods
  43. 43. 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
  44. 44. 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
  45. 45. 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/
  46. 46. 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
  47. 47. ThankYou! • Questions?

×