what you need to know about lambdas

49
@knight_cloud What You Need to Know About Lambdas Ryan Knight Senior Consultant | Typesafe

Upload: ryan-knight

Post on 10-May-2015

256 views

Category:

Technology


1 download

DESCRIPTION

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

TRANSCRIPT

Page 1: What You Need to Know about Lambdas

@knight_cloud

What You Need to Know About Lambdas

Ryan KnightSenior Consultant | Typesafe

Page 2: What You Need to Know about Lambdas

Acknowledgement

• Material originally created by Jamie Allen

• @jamie_allen

• Modified by Ryan Knight

• @knight_cloud

Page 3: What You Need to Know about Lambdas
Page 4: What You Need to Know about Lambdas

I Love Functional Programming!

• Functional Programming is:

• Immutability

• Referential Transparency

• Functions as first-class citizens

• Eliminating side effects

Page 5: What You Need to Know about Lambdas

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

Page 6: What You Need to Know about Lambdas

Imperative Codefinal 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); }

Page 7: What You Need to Know about Lambdas

We Want Declarative Code

• Remove temporary lists - List<Integer> numbersPlusOne = Collections.emptyList();

• Remove looping - for (Integer number : numbers)

• Focus on what - x+1

Page 8: What You Need to Know about Lambdas

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

Page 9: What You Need to Know about Lambdas

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

Page 10: What You Need to Know about Lambdas

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

Page 11: What You Need to Know about Lambdas

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()); } }

λ

Page 12: What You Need to Know about Lambdas

Scala

• Have a Function Type used to represent the Lambda

• map(f:Integer => Integer)

• Creates a Function underneath the covers

Page 13: What You Need to Know about Lambdas

Scala

object LambdaDemo extends App { val numbers = List(1, 2, 3) val numbersPlusOne = numbers.map(number => number + 1) }

λ

Page 14: What You Need to Know about Lambdas

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

λ

Page 15: What You Need to Know about Lambdas

Clojure

(ns LambdaDemo.core) (defn -main [& args] (println(map #(+ % 1) [1, 2, 3])))

λ

Page 16: What You Need to Know about Lambdas

JRuby

require "java" !array = [1, 2, 3] array.collect! do |n| n + 1 end

λ

Page 17: What You Need to Know about Lambdas

What is the Problem?

Page 18: What You Need to Know about Lambdas

There Are Caveats

Page 19: What You Need to Know about Lambdas

Not Reusable

• Lambdas are limited in scope to their call site

• You cannot reuse the functionality elsewhere

Page 20: What You Need to Know about Lambdas

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

Page 21: What You Need to Know about Lambdas

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

Page 22: What You Need to Know about Lambdas

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)  

}

} }}}

λλλλλ

Page 23: What You Need to Know about Lambdas
Page 24: What You Need to Know about Lambdas

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

Page 25: What You Need to Know about Lambdas

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

Page 26: What You Need to Know about Lambdas

Nashorn Javascript

list.parallelStream(). map(function(e) e / 0) ![1, 2, 3] Infinity,Infinity

Page 27: What You Need to Know about Lambdas

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

Page 28: What You Need to Know about Lambdas

Scalaval 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

Page 29: What You Need to Know about Lambdas

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

Page 30: What You Need to Know about Lambdas

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

Page 31: What You Need to Know about Lambdas

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!

Page 32: What You Need to Know about Lambdas

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());

Page 33: What You Need to Know about Lambdas

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)

Page 34: What You Need to Know about Lambdas

Solution

We want to maintain our ability to program in a functional style, while having something

maintainable and understandable in production

Page 35: What You Need to Know about Lambdas

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

Page 36: What You Need to Know about Lambdas

Named Function

final Function<Integer, Integer> addOneToValue = number -> number / 0; !final List<Integer> numbersPlusOne = numbers.stream() .map(addOneToValue) .collect(Collectors.toList());

Page 37: What You Need to Know about Lambdas

Named Function Stack Tracefinal 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

Page 38: What You Need to Know about Lambdas

“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

Page 39: What You Need to Know about Lambdas

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!

Page 40: What You Need to Know about Lambdas

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

Page 41: What You Need to Know about Lambdas

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

Page 42: What You Need to Know about Lambdas

Stack Trace of a Stable Methoddef 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!

Page 43: What You Need to Know about Lambdas

Benefits• You can’t close over variables

• Better stack traces

• More debuggable

• More testable

• More maintainable and descriptive

• Reusable

Page 44: What You Need to Know about Lambdas

Rule of Thumb

• Reserve lambda usage for the most basic expressions

• Externalize anything more significant than that to methods

Page 45: What You Need to Know about Lambdas

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

Page 46: What You Need to Know about Lambdas

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

Page 47: What You Need to Know about Lambdas

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/

Page 48: What You Need to Know about Lambdas

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

Page 49: What You Need to Know about Lambdas

Thank You!

• Questions?