33
What You Need To Know About

What you need to know about Lambdas - Jamie Allen

  • 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

Page 1: What you need to know about Lambdas - Jamie Allen

What You Need To Know About

Page 2: What you need to know about Lambdas - Jamie Allen

@jamie_allen

Jamie  Allen

•Director  of  Consulting  

•User  of  Lambdas  on  the  JVM  for  4+  years

Page 3: What you need to know about Lambdas - Jamie Allen

@jamie_allen

I  Love  Functional  Programming!

•Functional  Programming  is:

•Immutable  state

•Referential  transparency

•Functions  as  first-­‐class  citizens

Page 4: What you need to know about Lambdas - Jamie Allen

@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!

Page 5: What you need to know about Lambdas - Jamie Allen

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

λ

Page 6: What you need to know about Lambdas - Jamie Allen

@jamie_allen

Scala

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

λ

Page 7: What you need to know about Lambdas - Jamie Allen

@jamie_allen

Clojure

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

λ

Page 8: What you need to know about Lambdas - Jamie Allen

@jamie_allen

JRuby

require "java"

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

λ

Page 9: What you need to know about Lambdas - Jamie Allen

@jamie_allen

So  What  Is  The  Problem?

Page 10: What you need to know about Lambdas - Jamie Allen

@jamie_allen

Not  Reusable

•Lambdas  are  limited  in  scope  to  their  call  site

•You  cannot  reuse  the  functionality  elsewhere

Page 11: What you need to know about Lambdas - Jamie Allen

@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

Page 12: What you need to know about Lambdas - Jamie Allen

@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

Page 13: What you need to know about Lambdas - Jamie Allen

@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

Page 14: What you need to know about Lambdas - Jamie Allen

@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

Page 15: What you need to know about Lambdas - Jamie Allen

@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

Page 16: What you need to know about Lambdas - Jamie Allen

@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

Page 17: What you need to know about Lambdas - Jamie Allen

@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

Page 18: What you need to know about Lambdas - Jamie Allen

@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

Page 19: What you need to know about Lambdas - Jamie Allen

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

Page 20: What you need to know about Lambdas - Jamie Allen

@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

Page 21: What you need to know about Lambdas - Jamie Allen

@jamie_allen

SOLUTION

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

Page 22: What you need to know about Lambdas - Jamie Allen

@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

Page 23: What you need to know about Lambdas - Jamie Allen

@jamie_allen

Named  Function

object LambdaTest extends App { val addOneToValue = (x: Int) => x + 1 val myList = (1 to 20).map(addOneToValue(_)) println(myList)}

Page 24: What you need to know about Lambdas - Jamie Allen

@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

Page 25: What you need to know about Lambdas - Jamie Allen

@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

Page 26: What you need to know about Lambdas - Jamie Allen

@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?

Page 27: What you need to know about Lambdas - Jamie Allen

@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!

Page 28: What you need to know about Lambdas - Jamie Allen

@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

Page 29: What you need to know about Lambdas - Jamie Allen

@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!

Page 30: What you need to know about Lambdas - Jamie Allen

@jamie_allen

Benefits•Can’t  close  over  variables

•Internal  variables  are  operands

•Better  stack  traces

•More  debuggable

•More  testable

•Easier  maintenance

•Reusability

Page 31: What you need to know about Lambdas - Jamie Allen

@jamie_allen

Rule  of  Thumb

•Keep  lambda  usage  for  the  smallest  expressions

•Externalize  anything  more  than  the  most  basic  logic  into  methods

Page 32: What you need to know about Lambdas - Jamie Allen

@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

Page 33: What you need to know about Lambdas - Jamie Allen

@jamie_allen

Thank  You!

•Questions?