+ All Categories
Home > Technology > 20160520 what youneedtoknowaboutlambdas

20160520 what youneedtoknowaboutlambdas

Date post: 21-Jan-2018
Category:
Upload: shinolajla
View: 750 times
Download: 0 times
Share this document with a friend
39
What You Need to Know About Lambdas
Transcript

What You Need to Know About Lambdas

I Love Functional Programming!

• Functional Programming is:

• Immutability

• Referential Transparency

• Functions as first-class citizens

We Want Declarative 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); }

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

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

λ

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

λ

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

λ

JRuby

require "java"

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

λ

What is the Problem?

There Are Caveats

Not Reusable

• Lambdas are limited in scope to their call site

• You cannot reuse the functionality elsewhere

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

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

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)  

}

} }}}

λλλλλ

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

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

Nashorn Javascript

list.parallelStream(). map(function(e) e / 0)

[1, 2, 3] Infinity,Infinity

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

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

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

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

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!

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

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)

Solution

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

maintainable and understandable in production

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

Named Function

object LambdaTest extends App { val addOneToValue = (x: Int) => x + 1

val myList = (1 to 20).map(addOneToValue) }

Named Function Stack Traceval badFunction = (x: Int) => x / 0 val myList = (1 to 20).map(badFunction)

Exception in thread "main" java.lang.ArithmeticException: / by zeroat 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

“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

Stack Trace of a Methoddef badFunction = (x: Int) => x / 0 val myList = (1 to 20).map(badFunction)

Exception in thread "main" java.lang.ArithmeticException: / by zeroat 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

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

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

Benefits• You can’t close over variables

• Better stack traces

• More debuggable

• More testable

• More maintainable and descriptive

• Reusable

Rule of Thumb

• Reserve lambda usage for the most basic expressions

• Externalize anything more significant than that to methods

Thank You!


Recommended