Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional Groovy

Functional Groovy

There are many advantages to writing programs using a functional style. Groovy is a multi-faceted language which supports both functional and imperative styles of programming. This talk looks at how to use Groovy while adhering to the most popular functional programming idioms.

Topics covered include using closures, currying and partial evaluation, closure composition, Groovy meta-programming and type checking tricks for the functional programmer, trampolining, using Java functional libraries, immutable data structures, lazy and infinite lists and leveraging Java8 lambdas.

paulking

July 27, 2018
Tweet

More Decks by paulking

Other Decks in Programming

Transcript

  1. objectcomputing.com © 2018, Object Computing, Inc. (OCI). All rights reserved.

    No part of these notes may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior, written permission of Object Computing, Inc. (OCI) Functional Groovy Gr8conf 2018 Dr Paul King @paulk_asert https://github.com/paulk-asert/functional-groovy https://speakerdeck.com/paulk/functional-groovy
  2. objectcomputing.com © 2018, Object Computing, Inc. (OCI). All rights reserved.

    2 WE ARE SOFTWARE ENGINEERS. We deliver mission-critical software solutions that accelerate innovation within your organization and stand up to the evolving demands of your business. • 160+ engineers • Home of Grails • Friend of Groovy • Global Footprint
  3. What is functional programming? A programming paradigm that emphasizes functions

    • We’ll elaborate later what “emphasizes” might mean • Groovy Closures provide first-class functional support: • Pure functions • Functions that interact with their environment • Rich capabilities: composition, partial application/currying, default argument values, static or duck typing, higher-order usage • Leverage Java-inspired functional capabilities • Lambda expressions, method references and streams (JDK8+) • Static methods in utility classes
  4. What is functional programming? A programming paradigm that emphasizes functions

    • But Groovy doesn’t enforce functional programming • Often a matter of style • Applying certain conventions • Avoiding certain practices
  5. Arguments can be named or rely on the default it

    name Arguments can be typed or untyped Can have no arguments Closures can be called using their call method or without Closures... © Paul King 2006-2018 def twice = { int num -> num + num } assert twice(5) == 10
  6. Methods can be converted to method closures ...Closures... © Paul

    King 2006-2018 // ... def twiceMethod(int num) { num * 2 } assert twiceMethod(2) == 4 def alsoTwice = this.&twiceMethod assert alsoTwice(5) == 10 def g3Twice = this::twiceMethod assert g3Twice(10) == 20 // ...
  7. ...Closures... © Paul King 2006-2018 // ... def quadruple =

    twice >> twice assert quadruple(5) == 20 def forty = quadruple.curry(10) assert forty() == 40 Default arg values Forward and backward composition Partial application (curry, rcurry, ncurry) Functions as first-class citizens: variables passed as parameters return types within data structures
  8. A closure is “more powerful” than a pure function Free

    variables are bound to the variables from their environment ...Closures... © Paul King 2006-2018 import groovy.util.logging.Log @Log class Quiz { private static int THE_ANSWER = 42 def checkAnswer = { int guess -> log.info "Guess was $guess" guess == THE_ANSWER } } def q = new Quiz() assert !q.checkAnswer(21) assert q.checkAnswer(twice(21)) def twice = { int num -> num + num } assert twice(5) == 10
  9. A closure is “more powerful” than a pure function Free

    variables are bound to the variables from their environment ...Closures... © Paul King 2006-2018 import groovy.util.logging.Log @Log class Quiz { private static int THE_ANSWER = 42 def checkAnswer = { int guess -> log.info "Guess was $guess" guess == THE_ANSWER } } def q = new Quiz() assert !q.checkAnswer(21) assert q.checkAnswer(twice(21)) def twice = { int num -> num + num } assert twice(5) == 10 def x = 4 def incrementWithSideEffect = { arg -> x++; arg + 1 } assert 11 == incrementWithSideEffect(10) assert 101 == incrementWithSideEffect(100) assert x == 6
  10. ...Closures Used for many things in Groovy: • Iterators •

    Callbacks • Higher-order functions • Specialized control structures • Builders • Dynamic method definition • Resource allocation • Threads • Continuation-like coding © Paul King 2006-2018
  11. Applying Closures and functional style © Paul King 2006-2018 import

    groovy.transform.CompileStatic @CompileStatic Number fac(Number n) { n == 1 ? 1G : n * fac(n - 1) } def nums = 100..200 def isPrime = { Number n -> (fac(n - 1) + 1) % n == 0 } def isPalindrome = { Number n -> n.toString().reverse() == n.toString() } def primes = [] def palins = [] def both = [] // calculate // primes, // palins, and // both println primes println palins println both Case study applying Closures but starting with an imperative style
  12. Applying Closures and functional style © Paul King 2006-2018 import

    groovy.transform.CompileStatic @CompileStatic Number fac(Number n) { n == 1 ? 1G : n * fac(n - 1) } def nums = 100..200 def isPrime = { Number n -> (fac(n - 1) + 1) % n == 0 } def isPalindrome = { Number n -> n.toString().reverse() == n.toString() } def primes = [] def palins = [] def both = [] for (int i = 0; i < nums.size(); i++) { def n = nums[i] if (isPrime(n)) primes << n if (isPalindrome(n)) palins << n if (isPrime(n) && isPalindrome(n)) both << n } println primes println palins println both Case study applying Closures but starting with an imperative style
  13. Applying Closures and functional style © Paul King 2006-2018 @groovy.transform.CompileStatic

    Number fac(Number n) { n == 1 ? 1G : n * fac(n - 1) } def nums = 100..200 def isPrime = { Number n -> (fac(n - 1) + 1) % n == 0 } def isPalindrome = { Number n -> n.toString().reverse() == n.toString() } def (primes, palins, both) = [[], [], []] for (int i = 0; i < nums.size(); i++) { def n = nums[i] boolean prime = isPrime(n) boolean palin = isPalindrome(n) if (prime) primes << n if (palin) palins << n if (prime && palin) both << n } println primes println palins println both Case study applying Closures; imperative style but now with an efficiency improvement
  14. Applying Closures and functional style © Paul King 2006-2018 import

    groovy.transform.CompileStatic @CompileStatic Number fac(Number n) { n == 1 ? 1G : n * fac(n - 1) } def nums = 100..200 def isPrime = { Number n -> (fac(n - 1) + 1) % n == 0 } def isPalindrome = { Number n -> n.toString().reverse() == n.toString() } def primes = nums.findAll(isPrime) def palins = nums.findAll(isPalindrome) def both = nums.findAll{ n -> isPalindrome(n) && isPrime(n) } println primes println palins println both Case study applying Closures but using Groovy’s map, filter, reduce operations on collections but what about efficiency and what if you only wanted to process nums once
  15. Applying Closures and functional style © Paul King 2006-2018 import

    groovy.transform.CompileStatic @CompileStatic Number fac(Number n) { n == 1 ? 1G : n * fac(n - 1) } def nums = 100..200 def isPrime = { Number n -> (fac(n - 1) + 1) % n == 0 } def isPalindrome = { Number n -> n.toString().reverse() == n.toString() } def primes = nums.findAll(isPrime) def palins = nums.findAll(isPalindrome) def both = primes.intersect(palins) println primes println palins println both Case study applying Closures but using Groovy’s map, filter, reduce operations on collections and an alternative efficiency improvement
  16. Applying Closures and functional style © Paul King 2006-2018 import

    groovy.transform.CompileStatic @CompileStatic Number fac(Number n) { n == 1 ? 1G : n * fac(n - 1) } def nums = 100..200 def isPrime = { Number n -> (fac(n - 1) + 1) % n == 0 } def isPalindrome = { Number n -> n.toString().reverse() == n.toString() } def (primes, palins, both) = nums.inject([[], [], []]) { acc, n -> def pr = isPrime(n) def pa = isPalindrome(n) if (pr) acc[0] << n if (pa) acc[1] << n if (pr && pa) acc[2] << n acc } println primes println palins println both Case study applying Closures but using Groovy’s map, filter, reduce operations and processing source collection once
  17. What is functional programming? A programming paradigm that focuses on

    functions • Prefer code that embodies what rather than how • Declarative over imperative • Recursion over iteration • Control side effects (distinguish pure and impure code) • Often easier to reason about • Additional reuse possibilities • Leverage lazy evaluation, memoization and concurrency • Referential integrity • Manage immutability • Prefer values over variables • Immutable objects/collections • Functional data structures
  18. objectcomputing.com © 2018, Object Computing, Inc. (OCI). All rights reserved.

    36 1. Introduction 2. Pure Functions 3. Immutability 4. Functional Data Structures 5. Streams/Lazy evaluation 6. Recursion 7. Advanced Abstractions AGENDA
  19. Are these both pure functions? Pure Functions, Closures and side-effects

    © Paul King 2006-2018 def x = 4 def increment = { arg -> arg + 1 } def incrementWithSideEffect = { arg -> x++; arg + 1 }
  20. Are these both pure functions? Pure Functions, Closures and side-effects

    © Paul King 2006-2018 def x = 4 def increment = { arg -> arg + 1 } assert 11 == increment(10) assert x == 4 def incrementWithSideEffect = { arg -> x++; arg + 1 } assert 11 == incrementWithSideEffect(10) assert 101 == incrementWithSideEffect(100) assert x == 6
  21. Are these both pure functions? Pure Functions, Closures and side-effects

    © Paul King 2006-2018 def x = 4 def increment = { arg -> arg + 1 } assert 11 == increment(10) assert x == 4 def incrementWithSideEffect = { arg -> x++; arg + 1 } assert 11 == incrementWithSideEffect(10) assert 101 == incrementWithSideEffect(100) assert x == 6
  22. Potential inputs: • Parameters • Instance state • Static/global state

    Potential outputs: • Return value • Instance state • Static/global state • Mutable params • Exceptions Pure Functions, Closures and side-effects © Paul King 2006-2018 def x = 4 def increment = { arg -> arg + 1 } assert 11 == increment(10) assert x == 4 def incrementWithSideEffect = { arg -> x++; arg + 1 } assert 11 == incrementWithSideEffect(10) assert 101 == incrementWithSideEffect(100) assert x == 6 Impure ≠ Evil • But more difficult to reason about
  23. Potential inputs: • Parameters • Instance state • Static/global state

    Potential outputs: • Return value • Instance state • Static/global state • Mutable params • Exceptions Pure Functions, Closures and side-effects © Paul King 2006-2018 def x = 4 def increment = { arg -> arg + 1 } assert 11 == increment(10) assert x == 4 def incrementWithSideEffect = { arg -> x++; arg + 1 } assert 11 == incrementWithSideEffect(10) assert 101 == incrementWithSideEffect(100) assert x == 6 Context Context Impure ≠ Evil • Need to understand context
  24. Potential inputs: • Parameters • Instance state • Static/global state

    Potential outputs: • Return value • Instance state • Static/global state • Mutable params • Exceptions Prefer pure functions; Avoid side-effects © Paul King 2006-2018 def x = 4 def increment = { arg -> arg + 1 } assert 11 == increment(10) assert x == 4 def incrementWithSideEffect = { arg -> x++; arg + 1 } assert 11 == incrementWithSideEffect(10) assert 101 == incrementWithSideEffect(100) assert x == 6 Impure ≠ Evil • Avoid when possible • Use with caution when needed
  25. © Paul King 2006-2018 Referential Transparency def x, y, z,

    arg def doSomething = { // ... } y = 3 x = y + 1 doSomething(y) z = y + 1 // expect z == x assert x == z Will this always execute successfully?
  26. © Paul King 2006-2018 Referential Transparency def x, y, z,

    arg def doSomething = { it = 99 // or it++ } y = 3 x = y + 1 doSomething(y) z = y + 1 // expect z == x assert x == z Will this always execute successfully?
  27. © Paul King 2006-2018 Referential Transparency def x, y, z,

    arg def doSomething = { y = 99 // or y++ } y = 3 x = y + 1 doSomething(y) z = y + 1 // expect z == x assert x == z Will this always execute successfully?
  28. © Paul King 2006-2018 Referential Transparency def x, y, z,

    arg def doSomething = { it.clear() } y = [1, 2, 3] x = y.size() doSomething(y) z = y.size() // expect z == x assert x == z Will this always execute successfully?
  29. © Paul King 2006-2018 Referential Transparency def x, y, z,

    arg def doSomethingPure = { // ... } y = 3 x = y + 1 doSomethingPure(y) z = y + 1 // require z == x assert x == z Referentially transparent: expressions can be evaluated at any time, one can freely replace variables by their values and vice-versa; always return the same result given the same inputs Equal expressions can always be substituted Leads to automatic memorization, automatic concurrency, more reuse through functional abstractions
  30. © Paul King 2006-2018 Referential Transparency def pythagorian(x, y) {

    Math.sqrt(x * x + y * y) } final int A = 4 final int B = 3 def c = pythagorian(A, B) // c = 5 assert c == 5 Referentially transparent: expressions can be evaluated at any time, one can freely replace variables by their values and vice-versa; always return the same result given the same inputs Equal expressions can always be substituted Leads to automatic memorization, automatic concurrency, more reuse through functional abstractions
  31. © Paul King 2006-2018 Referential Transparency def c = 5

    // replaced at compile time assert c == 5 Referentially transparent: expressions can be evaluated at any time, one can freely replace variables by their values and vice-versa; always return the same result given the same inputs Equal expressions can always be substituted Leads to automatic memorization, automatic concurrency, more reuse through functional abstractions
  32. © Paul King 2006-2018 Referential Transparency assert true Referentially transparent:

    expressions can be evaluated at any time, one can freely replace variables by their values and vice-versa; always return the same result given the same inputs Equal expressions can always be substituted Leads to automatic memorization, automatic concurrency, more reuse through functional abstractions
  33. © Paul King 2006-2018 Referential Transparency and Laziness def s

    = [2, 5, 3, 2, 0, 2] as Stack assert s.size() % 2 == 0 def result = [] while(s.size() > 0) { result << pow(s.pop(), s.pop()) } assert result == [1, 8, 25] assert s.size() == 0 //def pow(base, exponent) { base ** exponent } def pow(base, exponent) { exponent == 0 ? 1 : base ** exponent } First version evaluates pow parameters eagerly and according to Groovy’s laws for parameter evaluation order Hint: they are the same as for Java
  34. © Paul King 2006-2018 Referential Transparency and Laziness def s

    = [2, 5, 3, 2, 0, 2] as Stack assert s.size() % 2 == 0 def result = [] def lazyPop = { s.pop() } while(s.size() > 0) { result << pow(lazyPop, lazyPop) } assert result == [1, 8, 25] & s.size() == 0 def pow(base, exponent) { base() ** exponent() } Lazy evaluation makes everything better, so let’s use it here All good so far, but we’ve left off the optimization
  35. © Paul King 2006-2018 Referential Transparency and Laziness def s

    = [2, 5, 3, 2, 0, 2] as Stack assert s.size() % 2 == 0 def result = [] def lazyPop = { s.pop() } while(s.size() > 0) { result << pow(lazyPop, lazyPop) } assert result == [1, 8, 25] & s.size() == 0 def pow(base, exponent) { exponent() == 0 ? 1 : base() ** exponent() }
  36. © Paul King 2006-2018 Referential Transparency and Laziness def s

    = [2, 5, 3, 2, 0, 2] assert s.size() % 2 == 0 def result = [] def lazyPop = { s.pop() } while(s.size() > 0) { result << pow(lazyPop, lazyPop) } assert result == [1, 8, 25] & s.size() == 0 def pow(base, exponent) { exponent() == 0 ? 1 : base() ** exponent() }
  37. © Paul King 2006-2018 Referential Transparency and Laziness def s

    = [2, 5, 3, 2, 0, 2] as Stack assert s.size() % 2 == 0 def result = [] def lazyPop = { s.pop() } while(s.size() > 0) { result << pow(lazyPop, lazyPop) } assert result == [1, 8, 25] & s.size() == 0 def pow(base, exponent) { def e = exponent() e == 0 ? 1 : base() ** e }
  38. © Paul King 2006-2018 Referential Transparency and Laziness def s

    = [2, 5, 3, 2, 0, 2] assert s.size() % 2 == 0 def result = [] def lazyPop = { s.pop() } while(s.size() > 0) { result << pow(lazyPop, lazyPop) } assert result == [1, 8, 25] & s.size() == 0 def pow(base, exponent) { def e = exponent() e == 0 ? 1 : base() ** e }
  39. © Paul King 2006-2018 Referential Transparency and Laziness def s

    = [2, 5, 3, 2, 0, 2] as Stack assert s.size() % 2 == 0 def result = [] def lazyPop = { s.pop() } while(s.size() > 0) { result << pow(lazyPop, lazyPop) } assert result == [1, 8, 25] & s.size() == 0 def pow(base, exponent) { def b = base() def e = exponent() e == 0 ? 1 : b ** e }
  40. © Paul King 2006-2018 Referential Transparency and Laziness def s

    = [2, 5, 3, 2, 0, 2] as Stack assert s.size() % 2 == 0 def get = { idx -> s[idx] } def baseIndices = s.indices.findAll{ it % 2 == 0 }.reverse() def result = baseIndices.collect{ pow(get.curry(it+1), get.curry(it)) } assert result == [1, 8, 25] def pow(base, exponent) { exponent() == 0 ? 1 : base() ** exponent() } But better to use referentially transparent version and no mutable structure in the first place. This version doesn’t leave stack empty but could be executed concurrently.
  41. © Paul King 2006-2018 Applying pure functions: memoization def wotd

    = 'possessionlessness' println wotd.size() println wotd.toSet().size() def upper = { c -> sleep 500; c.toUpperCase() } timedWotd(wotd, upper) timedWotd(wotd, upper.memoize()) // also memoizeAtLeast(5), memoizeBetween(5, 10) timedWotd(wotd, upper.memoizeAtMost(5)) private void timedWotd(String wotd, upper) { def start = System.currentTimeMillis() def result = wotd.collect { upper(it) } def time = System.currentTimeMillis() - start assert result == ['P', 'O', 'S', 'S', 'E', 'S', 'S', 'I','O', 'N', 'L', 'E', 'S', 'S', 'N', 'E', 'S', 'S'] println time } Memorize results for pure functions Don’t try with Math.random, Coin.toss, Dice.throw, … 18 7 9008 3502 4503
  42. © Paul King 2006-2018 Memoization for methods too import groovy.transform.Memoized

    @Memoized int sum(int n1, int n2) { println "DEBUG: $n1 + $n2 = ${n1 + n2}" n1 + n2 } 2.times { [[1, 10], [2, 20]].each { a, b -> println sum(a, b) } } DEBUG: 1 + 10 = 11 11 DEBUG: 2 + 20 = 22 22 11 22
  43. objectcomputing.com © 2018, Object Computing, Inc. (OCI). All rights reserved.

    64 1. Introduction 2. Pure Functions 3. Immutability 4. Functional Data Structures 5. Streams/Lazy evaluation 6. Recursion 7. Advanced Abstractions AGENDA
  44. Why Immutability? Simple • Exactly one state • Potentially easier

    to design, implement, use, reason about & make secure Inherently referentially transparent • Potential for optimisation Can be shared freely • Including “constant” aggregations of immutables • Including persistent structures of immutables • Suitable for caching • Can even cache “pure” expressions involving immutables, e.g. 3 + 4, “string”.size(), fib(42) • Inherently thread safe
  45. Immutable practices Avoid using mutator methods • But only marginal

    gains when using Java’s built-in collections // Avoid String invention = 'Mouse Trap' List inventions = [invention] invention = 'Better ' + invention inventions << invention assert inventions == ['Mouse Trap', 'Better Mouse Trap'] inventions.removeAll 'Mouse Trap' assert inventions == ['Better Mouse Trap'] // Prefer String firstInvention = 'Mouse Trap' List initialInventions = [firstInvention] String secondInvention = 'Better ' + firstInvention List allInventions = initialInventions + secondInvention assert allInventions == ['Mouse Trap', 'Better Mouse Trap'] List bestInventions = allInventions - firstInvention assert bestInventions == ['Better Mouse Trap']
  46. Immutable practices Avoid using mutator methods Avoid Prefer list.sort() list.toSorted()

    list.sort(false) list.unique() list.toUnique() list.unique(false) list.reverse(true) list.reverse() list.addAll list.plus list.removeAll list.minus String or List += or << use differently named variables mutating java.util.Collections void methods, e.g. shuffle, swap, fill, copy, rotate your own non mutating variants
  47. Immutable practices Avoid using mutator methods Avoid Prefer list.sort() list.toSorted()

    list.sort(false) list.unique() list.toUnique() list.unique(false) list.reverse(true) list.reverse() list.addAll list.plus list.removeAll list.minus String or List += or << use differently named variables mutating java.util.Collections void methods, e.g. shuffle, swap, fill, copy, rotate your own non mutating variants public class Collections { public static void shuffle(List<?> list) { /* ... */ } /* ... */ }
  48. Immutable practices Avoid using mutator methods Avoid Prefer list.sort() list.toSorted()

    list.sort(false) list.unique() list.toUnique() list.unique(false) list.reverse(true) list.reverse() list.addAll list.plus list.removeAll list.minus String or List += or << use differently named variables mutating java.util.Collections void methods, e.g. shuffle, swap, fill, copy, rotate your own non mutating variants static List myShuffle(List list) { List result = new ArrayList(list) Collections.shuffle(result) result }
  49. Immutability options - collections Built-in Google Collections • Numerous improved

    immutable collection types Groovy run-time metaprogramming import com.google.common.collect.* List<String> animals = ImmutableList.of("cat", "dog", "horse") animals << 'fish' // => java.lang.UnsupportedOperationException def animals = ['cat', 'dog', 'horse'].asImmutable() animals << 'fish' // => java.lang.UnsupportedOperationException def animals = ['cat', 'dog', 'horse'] ArrayList.metaClass.leftShift = { throw new UnsupportedOperationException() } animals << 'fish' // => java.lang.UnsupportedOperationException
  50. Immutable Classes Some Rules • Don’t provide mutators • Ensure

    that no methods can be overridden • Easiest to make the class final • Or use static factories & non-public constructors • Make all fields final • Make all fields private • Avoid even public immutable constants • Ensure exclusive access to any mutable components • Don’t leak internal references • Defensive copying in and out • Optionally provide equals and hashCode methods • Optionally provide toString method
  51. @Immutable... Java Immutable Class • As per Joshua Bloch Effective

    Java © Paul King 2006-2018 public final class Person { private final String first; private final String last; public String getFirst() { return first; } public String getLast() { return last; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((first == null) ? 0 : first.hashCode()); result = prime * result + ((last == null) ? 0 : last.hashCode()); return result; } public Person(String first, String last) { this.first = first; this.last = last; } // ... // ... @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (first == null) { if (other.first != null) return false; } else if (!first.equals(other.first)) return false; if (last == null) { if (other.last != null) return false; } else if (!last.equals(other.last)) return false; return true; } @Override public String toString() { return "Person(first:" + first + ", last:" + last + ")"; } }
  52. ...@Immutable... Java Immutable Class • As per Joshua Bloch Effective

    Java © Paul King 2006-2018 public final class Person { private final String first; private final String last; public String getFirst() { return first; } public String getLast() { return last; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((first == null) ? 0 : first.hashCode()); result = prime * result + ((last == null) ? 0 : last.hashCode()); return result; } public Person(String first, String last) { this.first = first; this.last = last; } // ... // ... @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (first == null) { if (other.first != null) return false; } else if (!first.equals(other.first)) return false; if (last == null) { if (other.last != null) return false; } else if (!last.equals(other.last)) return false; return true; } @Override public String toString() { return "Person(first:" + first + ", last:" + last + ")"; } } boilerplate
  53. objectcomputing.com © 2018, Object Computing, Inc. (OCI). All rights reserved.

    79 1. Introduction 2. Pure Functions 3. Immutability 4. Functional Data Structures 5. Streams/Lazy evaluation 6. Recursion 7. Advanced Abstractions AGENDA
  54. Approaches to managing collection storage Mutable © Paul King 2006-2018

    ‘c’ ‘a’ ‘c’ ‘a’ Add ‘t’ Add ‘t’ Add ‘t’ ‘c’ ‘a’ ‘t’ ‘c’ ‘a’ ‘c’ ‘a’ ‘t’ X ‘c’ ‘a’ ‘t’ ‘c’ ‘a’ Immutable Persistent
  55. Approaches to managing collection storage Mutable © Paul King 2006-2018

    ‘c’ ‘a’ ‘c’ ‘a’ Add ‘t’ Add ‘t’ Add ‘t’ ‘c’ ‘a’ ‘t’ ‘c’ ‘a’ ‘c’ ‘a’ ‘t’ X ‘c’ ‘a’ ‘t’ ‘c’ ‘a’ Persistent Immutable
  56. Approaches to managing collection storage Mutable © Paul King 2006-2018

    ‘c’ ‘a’ ‘c’ ‘a’ Add ‘t’ Add ‘t’ Add ‘t’ ‘c’ ‘a’ ‘t’ ‘c’ ‘a’ ‘c’ ‘a’ ‘t’ X ‘c’ ‘a’ ‘t’ ‘c’ ‘a’ Persistent Immutable
  57. Immutability – persistent collections Functional Java • Or Functional Groovy,

    clj-ds, pcollections, totallylazy @Grab('org.functionaljava:functionaljava:4.4') def pets = fj.data.List.list("cat", "dog", "horse") // buy a fish def newPets = pets.cons("fish") assert [3, 4] == [pets.length(), newPets.length()] pets newPets head tail head tail head tail head tail fish cat dog horse
  58. Immutability – persistent collections Functional Java @Grab('org.functionaljava:functionaljava:4.4') def pets =

    fj.data.List.list("cat", "dog", "horse") def newPets = pets.cons("fish") assert [3, 4] == [pets.length(), newPets.length()] // sell the horse def remaining = newPets.removeAll{ it == 'horse' } pets newPets head tail head tail head tail head tail fish cat dog horse remaining ???
  59. Immutability – persistent collections Functional Java @Grab('org.functionaljava:functionaljava:4.4') def pets =

    fj.data.List.list("cat", "dog", "horse") def newPets = pets.cons("fish") assert [3, 4] == [pets.length(), newPets.length()] def remaining = newPets.removeAll{ it == 'horse' } assert [3, 4, 3] == [pets, newPets, remaining]*.length() pets newPets head tail head tail head tail head tail fish cat dog horse remaining ???
  60. Immutability – persistent collections Functional Java @Grab('org.functionaljava:functionaljava:4.4') def pets =

    fj.data.List.list("cat", "dog", "horse") def newPets = pets.cons("fish") assert [3, 4] == [pets.length(), newPets.length()] def remaining = newPets.removeAll{ it == 'horse' } assert [3, 4, 3] == [pets, newPets, remaining]*.length() pets newPets head tail head tail head tail head tail fish cat dog horse remaining head tail head tail head tail fish cat dog copy copy copy
  61. Immutability – persistent collections You will see the correct results

    but in general, different operations may give very differing performance characteristics from what you expect • But don’t fret, smart people are working on smart structures to support a variety of scenarios. You may even have several in your current NoSQL implementation A B C D E F G H I A* C* G* K J original modified
  62. Persistent data structures: Functional Java/Groovy • Singly-linked list (fj.data.List) •

    Lazy singly-linked list (fj.data.Stream) • Nonempty list (fj.data.NonEmptyList) • Optional value (a container of length 0 or 1) (fj.data.Option) • Immutable set using a red/black tree (fj.data.Set) • Immutable multi-way tree (a.k.a. rose tree) (fj.data.Tree) • Immutable tree-map using a red/black tree (fj.data.TreeMap) • Products (tuples) of arity 1-8 (fj.P1..P8) • Vectors of arity 2-8 (fj.data.vector.V2..V8) • Pointed lists and trees (fj.data.Zipper and fj.data.TreeZipper) • Type-safe, generic heterogeneous list (fj.data.hlist.HList) • Immutable arrays (fj.data.Array) • Disjoint union datatype (fj.data.Either) • 2-3 finger trees supporting access to the ends in amortized O(1) time (fj.data.fingertrees)
  63. objectcomputing.com © 2018, Object Computing, Inc. (OCI). All rights reserved.

    96 1. Introduction 2. Pure Functions 3. Immutability 4. Functional Data Structures 5. Streams/Lazy evaluation 6. Recursion 7. Advanced Abstractions AGENDA
  64. Lazy evaluation Normal Boolean expression short-circuiting GString usage def myList

    = // ... if (myList != null && myList.size() > 1) { myList.remove(0) } def gs1 = "Hello at ${ new Date() }" def gs2 = "Hello at ${ -> new Date() }" sleep 5000 println gs1 println gs2
  65. Lazy evaluation – Ternary (TBD) Ternary Ternary revisited def myList

    = // ... if (myList != null && myList.size() > 1) { myList.remove(0) } ternary(condition, Closure c, Supplier s) { … }
  66. Lazy evaluation – Logging (TBD) Ternary Equiv: @Log4j Log.trace() def

    gs1 = "Hello at ${ new Date() }" def gs2 = "Hello at ${ -> new Date() }" sleep 5000 println gs1 println gs2
  67. Lazy evaluation – JDK8 Streams (TBD) Stream is a spec

    of how to manipulate data IntStream.iterate( 1, i -> i + 1) // potentially infinite .map( i -> i + 2 ) // lazy .filter( i -> i > 5 ) // lazy .findFirst() // terminal operation
  68. Lazy evaluation @Singleton @Singleton(lazy = true) class Universe { public

    int ANSWER = 42 } assert 42 == Universe.instance.ANSWER
  69. Lazy evaluation @Lazy // nominally expensive resource // with stats

    class Resource { private static alive = 0 private static used = 0 Resource() { alive++ } def use() { used++ } static stats() { "$alive alive, $used used" } } class ResourceMain { def res1 = new Resource() @Lazy res2 = new Resource() @Lazy static res3 = { new Resource() }() @Lazy(soft=true) volatile Resource res4 } new ResourceMain().with { assert Resource.stats() == '1 alive, 0 used' res2.use() res3.use() res4.use() assert Resource.stats() == '4 alive, 3 used' assert res4 instanceof Resource def expected = 'res4=java.lang.ref.SoftReference' assert it.dump().contains(expected) }
  70. totallylazy library Similar to Groovy’s collection GDK methods … Except

    … lazy … @GrabResolver('http://repo.bodar.com/') @Grab('com.googlecode.totallylazy:totallylazy:1113') import static com.googlecode.totallylazy.Sequences.map import static com.googlecode.totallylazy.numbers.Numbers.* assert range(6, 10) == [6,7,8,9,10] assert range(6, 10, 2).forAll(even) assert range(6, 10).reduce{ a, b -> a + b } == 40 assert range(6, 10).foldLeft(0, add) == 40 assert map(range(6, 10), { it + 100 }) == [106,107,108,109,110] assert primes().take(10) == [2,3,5,7,11,13,17,19,23,29] assert range(1, 4).cycle().drop(2).take(8) == [3,4,1,2,3,4,1,2] println range(6, 1_000_000_000_000).filter(even).drop(1).take(5) // => 8,10,12,14,16 (a handful of millis later)
  71. Immutability options - collections This script Produces this output (order

    will vary) @GrabResolver('http://repo.bodar.com/') @Grab('com.googlecode.totallylazy:totallylazy:1113') import static com.googlecode.totallylazy.Sequences.flatMapConcurrently import static com.googlecode.totallylazy.numbers.Numbers.* println flatMapConcurrently(range(6, 10)) { println it // just for logging even(it) ? [it, it+100] : [] } 9 7 8 6 10 6,106,8,108,10,110
  72. GPars and TotallyLazy library © Paul King 2006-2018 @GrabResolver('http://repo.bodar.com') @Grab('com.googlecode.totallylazy:totallylazy:1113')

    import static groovyx.gpars.GParsExecutorsPool.withPool import static com.googlecode.totallylazy.Callables.asString import static com.googlecode.totallylazy.Sequences.sequence withPool { pool -> assert ['5', '6'] == sequence(4, 5, 6) .drop(1) .mapConcurrently(asString(), pool) .toList() } withPool { assert ['5', '6'] == [4, 5, 6] .drop(1) .collectParallel{ it.toString() } } <= Plain GPars equivalent
  73. Groovy Streams https://github.com/timyates/groovy-stream @Grab('com.bloidonia:groovy-stream:0.5.2') import groovy.stream.Stream // Repeat an object

    indefinitely Stream s = Stream.from { 1 } assert s.take( 5 ).collect() == [ 1, 1, 1, 1, 1 ] // Use an Iterable s = Stream.from 1..3 assert s.collect() == [ 1, 2, 3 ] // Use an iterator def iter = [ 1, 2, 3 ].iterator() s = Stream.from iter assert s.collect() == [ 1, 2, 3 ] // Use a map of iterables s = Stream.from x:1..2, y:3..4 assert s.collect() == [ [x:1,y:3],[x:1,y:4],[x:2,y:3],[x:2,y:4] ]
  74. Groovy Streams https://github.com/dsrkoc/monadologie import static hr.helix.monadologie.MonadComprehension.foreach def res = foreach

    { a = takeFrom { [1, 2, 3] } b = takeFrom { [4, 5] } yield { a + b } } assert res == [5, 6, 6, 7, 7, 8]
  75. objectcomputing.com © 2018, Object Computing, Inc. (OCI). All rights reserved.

    111 1. Introduction 2. Pure Functions 3. Immutability 4. Functional Data Structures 5. Streams/Lazy evaluation 6. Recursion 7. Advanced Abstractions AGENDA @Grab('one.util:streamex:0.6.6')
  76. Recursion Divide and conquer strategy I don’t know how to

    solve this problem but I know how to break it into two smaller problems
  77. Recursion Divide and conquer strategy I don’t know how to

    solve this problem but I know how to break it into two smaller problems E.g. I don’t know how to solve: factorial(n) but I know it is: n * factorial(n – 1) and I know factorial(1) = 1
  78. Recursion © Paul King 2006-2018 def factorial = { it

    <= 1 ? 1G : it * factorial(it - 1) } println factorial(5) ?
  79. Recursion © Paul King 2006-2018 def factorial = { it

    <= 1 ? 1G : it * factorial(it - 1) } println factorial(5)
  80. Recursion © Paul King 2006-2018 def factorial = { it

    <= 1 ? 1G : it * factorial(it - 1) } def factorial factorial = { it <= 1 ? 1G : it * factorial(it - 1) } println factorial(5) def factorial = { it <= 1 ? 1G : it * call(it - 1) } println factorial(5)
  81. Recursion © Paul King 2006-2018 def factorial = { it

    <= 1 ? 1G : it * factorial(it - 1) } def factorial factorial = { it <= 1 ? 1G : it * factorial(it - 1) } println factorial(5) def factorial = { it <= 1 ? 1G : it * call(it - 1) } println factorial(5) factorial(5) 5 * factorial(4) 5 * (4 * factorial(3)) 5 * (4 * (3 * factorial(2))) 5 * (4 * (3 * (2 * factorial(1)))) 5 * (4 * (3 * (2 * 1))) 5 * (4 * (3 * 2)) 5 * (4 * 6) 5 * 24 120
  82. Recursion © Paul King 2006-2018 def factorial = { it

    <= 1 ? 1G : it * factorial(it - 1) } def factorial factorial = { it <= 1 ? 1G : it * factorial(it - 1) } println factorial(5) def factorial = { it <= 1 ? 1G : it * call(it - 1) } println factorial(5) Subject to StackOverflow factorial(5) 5 * factorial(4) 5 * (4 * factorial(3)) 5 * (4 * (3 * factorial(2))) 5 * (4 * (3 * (2 * factorial(1)))) 5 * (4 * (3 * (2 * 1))) 5 * (4 * (3 * 2)) 5 * (4 * 6) 5 * 24 120
  83. Recursion © Paul King 2006-2018 def factorial factorial = {

    n, acc -> n <= 1 ? acc : factorial(n - 1, n * acc) } println factorial(5, 1) factorial(5, 1) factorial(4, 5) factorial(3, 20) factorial(2, 60) factorial(1, 120) 120 def factorial = { n, acc = 1 -> n <= 1 ? acc : call(n - 1, n * acc) } println factorial(5) def factorial = { n -> def doFact = { n1, acc -> n1 <= 1 ? acc : call(n1 - 1, n1 * acc) } doFact(n, 1) } println factorial(5)
  84. Recursion © Paul King 2006-2018 def factorial factorial = {

    n, acc -> n <= 1 ? acc : factorial(n - 1, n * acc) } println factorial(5, 1) factorial(5, 1) factorial(4, 5) factorial(3, 20) factorial(2, 60) factorial(1, 120) 120 def factorial = { n, acc = 1 -> n <= 1 ? acc : call(n - 1, n * acc) } println factorial(5) def factorial = { n -> def doFact = { n1, acc -> n1 <= 1 ? acc : call(n1 - 1, n1 * acc) } doFact(n, 1) } println factorial(5)
  85. Recursion © Paul King 2006-2018 def factorial factorial = {

    n, acc -> n <= 1 ? acc : factorial(n - 1, n * acc) } println factorial(5, 1) def factorial = { n, acc = 1 -> n <= 1 ? acc : call(n - 1, n * acc) } println factorial(5) def factorial = { n -> def doFact = { n1, acc -> n1 <= 1 ? acc : call(n1 - 1, n1 * acc) } doFact(n, 1) } println factorial(5) Still subject to StackOverflow factorial(5, 1) factorial(4, 5) factorial(3, 20) factorial(2, 60) factorial(1, 120) 120
  86. Recursion © Paul King 2006-2018 def factorial factorial = {

    n, acc = 1G -> n <= 1 ? acc : factorial.trampoline(n - 1, n * acc) } println factorial(1) println factorial(5) println factorial(5).call() factorial(1) 1 factorial(5).call() tfactorial(4, 5).call() factorial(4, 5) tfactorial(3, 20).call() factorial(3, 20) tfactorial(2, 60).call() factorial(2, 60) tfactorial(1, 120).call() factorial(1, 120) 120 factorial(5) tfactorial(4, 5) groovy.lang.TrampolineClosure@50ad3bc1 trampoline() is a method available on the Closure class which returns a trampoline wrapped version of itself
  87. Recursion © Paul King 2006-2018 def factorial factorial = {

    n, acc = 1G -> n <= 1 ? acc : factorial.trampoline(n - 1, n * acc) } println factorial(1) println factorial(5) println factorial(5).call() factorial(1) 1 factorial(5).call() tfactorial(4, 5).call() factorial(4, 5) tfactorial(3, 20).call() factorial(3, 20) tfactorial(2, 60).call() factorial(2, 60) tfactorial(1, 120).call() factorial(1, 120) 120 factorial(5) tfactorial(4, 5) groovy.lang.TrampolineClosure@50ad3bc1 trampoline() is a method available on the Closure class which returns a trampoline wrapped version of itself
  88. Recursion © Paul King 2006-2018 def factorial factorial = {

    n, acc = 1G -> n <= 1 ? acc : factorial.trampoline(n - 1, n * acc) } println factorial(1) println factorial(5) println factorial(5).call() factorial(1) 1 factorial(5).call() tfactorial(4, 5).call() factorial(4, 5) tfactorial(3, 20).call() factorial(3, 20) tfactorial(2, 60).call() factorial(2, 60) tfactorial(1, 120).call() factorial(1, 120) 120 factorial(5) tfactorial(4, 5) groovy.lang.TrampolineClosure@50ad3bc1 Trampolined closure – if called, continually loops calling itself until a non-trampoline Closure result is found
  89. Recursion © Paul King 2006-2018 def factorial factorial = {

    n, acc = 1G -> n <= 1 ? acc : factorial.trampoline(n - 1, n * acc) }.trampoline() println factorial(1) println factorial(5) tfactorial(1) factorial(1) 1 tfactorial(5) factorial(5).call() tfactorial(4, 5) ... 120 Trampolined closure – if called, continually loops calling itself until a non-trampoline Closure result is found
  90. Mutual Recursion © Paul King 2006-2018 def even def odd

    = { n -> n == 0 ? false : even(n - 1) } even = { n -> n == 0 ? true : odd(n - 1) } assert odd(21) & even(12) def odd, even odd = { n -> n == 0 ? false : even.trampoline(n - 1) }.trampoline() even = { n -> n == 0 ? true : odd.trampoline(n - 1) }.trampoline() assert odd(4321) & even(1234) Subject to StackOverflow
  91. Mutual Recursion © Paul King 2006-2018 def even def odd

    = { n -> n == 0 ? false : even(n - 1) } even = { n -> n == 0 ? true : odd(n - 1) } assert odd(21) & even(12) def odd, even odd = { n -> n == 0 ? false : even.trampoline(n - 1) }.trampoline() even = { n -> n == 0 ? true : odd.trampoline(n - 1) }.trampoline() assert odd(4321) & even(1234)
  92. Recursion for methods © Paul King 2006-2018 import groovy.transform.TailRecursive @TailRecursive

    def factorial(n) { n * factorial(n - 1) } println factorial(10G) ?
  93. Recursion for methods © Paul King 2006-2018 import groovy.transform.TailRecursive @TailRecursive

    def factorial(n) { n * factorial(n - 1) } println factorial(10G)
  94. Recursion for methods © Paul King 2006-2018 import groovy.transform.TailRecursive @TailRecursive

    def factorial(n, acc = 1) { n <= 1 ? acc : factorial(n - 1, n * acc) } println factorial(1000G)
  95. Recursion for methods © Paul King 2006-2018 public java.lang.Object factorial(java.lang.Object

    n, java.lang.Object acc = 1) { java.lang.Object _acc_ = acc java.lang.Object _n_ = n while (true) { _RECUR_HERE_: try { if ( _n_ <= 1) { return _acc_ } else { null: { java.lang.Object __n__ = _n_ java.lang.Object __acc__ = _acc_ _n_ = __n__ - 1 _acc_ = __n__ * __acc__ continue _RECUR_HERE_ } } } catch (org.codehaus.groovy.transform.tailrec.GotoRecurHereException ignore) { continue _RECUR_HERE_ } finally { } } }
  96. objectcomputing.com © 2018, Object Computing, Inc. (OCI). All rights reserved.

    134 1. Introduction 2. Pure Functions 3. Immutability 4. Functional Data Structures 5. Streams/Lazy evaluation 6. Recursion 7. Advanced Abstractions AGENDA
  97. Word Split with Fortress © Paul King 2006-2018 Guy Steele’s

    StrangeLoop keynote (from slide 52 onwards for several slides): http://strangeloop2010.com/talk/presentation_file/14299/GuySteele-parallel.pdf
  98. Word Split… © Paul King 2006-2018 def swords = {

    s -> def result = [] def word = '' s.each{ ch -> if (ch == ' ') { if (word) result += word word = '' } else word += ch } if (word) result += word result } assert swords("This is a sample") == ['This', 'is', 'a', 'sample'] assert swords("Here is a sesquipedalian string of words") == ['Here', 'is', 'a', 'sesquipedalian', 'string', 'of', 'words']
  99. Word Split… © Paul King 2006-2018 def swords = {

    s -> def result = [] def word = '' s.each{ ch -> if (ch == ' ') { if (word) result += word word = '' } else word += ch } if (word) result += word result }
  100. Word Split… © Paul King 2006-2018 def swords = {

    s -> def result = [] def word = '' s.each{ ch -> if (ch == ' ') { if (word) result += word word = '' } else word += ch } if (word) result += word result }
  101. Segment(left1, m1, right1) Segment(left2, m2, right2) Segment(left1, m1 + [

    ? ] + m2, right2) …Word Split… © Paul King 2006-2018
  102. …Word Split… © Paul King 2006-2018 class Util { static

    maybeWord(s) { s ? [s] : [] } } import static Util.* @Immutable class Chunk { String s public static final ZERO = new Chunk('') def plus(Chunk other) { new Chunk(s + other.s) } def plus(Segment other) { new Segment(s + other.l, other.m, other.r) } def flatten() { maybeWord(s) } } @Immutable class Segment { String l; List m; String r public static final ZERO = new Segment('', [], '') def plus(Chunk other) { new Segment(l, m, r + other.s) } def plus(Segment other) { new Segment(l, m + maybeWord(r + other.l) + other.m, other.r) } def flatten() { maybeWord(l) + m + maybeWord(r) } }
  103. …Word Split… © Paul King 2006-2018 def processChar(ch) { ch

    == ' ' ? new Segment('', [], '') : new Chunk(ch) } def swords(s) { s.inject(Chunk.ZERO) { result, ch -> result + processChar(ch) } } assert swords("Here is a sesquipedalian string of words").flatten() == ['Here', 'is', 'a', 'sesquipedalian', 'string', 'of', 'words']
  104. …Word Split… © Paul King 2006-2018 THREADS = 4 def

    pwords(s) { int n = (s.size() + THREADS - 1) / THREADS def map = new ConcurrentHashMap() (0..<THREADS).collect { i -> Thread.start { def (min, max) = [ [s.size(), i * n].min(), [s.size(), (i + 1) * n].min() ] map[i] = swords(s[min..<max]) } }*.join() (0..<THREADS).collect { i -> map[i] }.sum().flatten() }
  105. …Word Split… © Paul King 2006-2018 import static groovyx.gpars.GParsPool.withPool THRESHHOLD

    = 10 def partition(piece) { piece.size() <= THRESHHOLD ? piece : [piece[0..<THRESHHOLD]] + partition(piece.substring(THRESHHOLD)) } def pwords = { input -> withPool(THREADS) { partition(input).parallel.map(swords).reduce{ a, b -> a + b }.flatten() } }
  106. …Guy Steele example in Groovy… © Paul King 2006-2018 def

    words = { s -> int n = (s.size() + THREADS - 1) / THREADS def min = (0..<THREADS).collectEntries{ [it, [s.size(),it*n].min()] } def max = (0..<THREADS).collectEntries{ [it, [s.size(),(it+1)*n].min()] } def result = new DataFlows().with { task { a = swords(s[min[0]..<max[0]]) } task { b = swords(s[min[1]..<max[1]]) } task { c = swords(s[min[2]..<max[2]]) } task { d = swords(s[min[3]..<max[3]]) } task { sum1 = a + b } task { sum2 = c + d } task { sum = sum1 + sum2 } println 'Tasks ahoy!' sum } switch(result) { case Chunk: return maybeWord(result.s) case Segment: return result.with{ maybeWord(l) + m + maybeWord(r) } } } DataFlow version: partially hard-coded to 4 partitions for easier reading
  107. …Guy Steele example in Groovy… © Paul King 2006-2018 GRANULARITY_THRESHHOLD

    = 10 THREADS = 4 println GParsPool.withPool(THREADS) { def result = runForkJoin(0, input.size(), input){ first, last, s -> def size = last - first if (size <= GRANULARITY_THRESHHOLD) { swords(s[first..<last]) } else { // divide and conquer def mid = first + ((last - first) >> 1) forkOffChild(first, mid, s) forkOffChild(mid, last, s) childrenResults.sum() } } switch(result) { case Chunk: return maybeWord(result.s) case Segment: return result.with{ maybeWord(l) + m + maybeWord(r) } } } Fork/Join version
  108. …Guy Steele example in Groovy © Paul King 2006-2018 println

    GParsPool.withPool(THREADS) { def ans = input.collectParallel{ processChar(it) }.sum() switch(ans) { case Chunk: return maybeWord(ans.s) case Segment: return ans.with{ maybeWord(l) + m + maybeWord(r) } } } Just leveraging the algorithm’s parallel nature
  109. Monadic style intro © Paul King 2006-2018 def unit =

    x -> [x, ''] println sineCubeDebug(unit(3)) println sineCubeDebug << unit << 3 def round = x -> Math.round(x) println round(1.23) def roundDebug = x -> unit(round(x)) println roundDebug << 1.23 // inspired by: // http://blog.klipse.tech/javascript/2016/08/31/monads-javascript.html Current example has been abbreviated to not really require Monadic capabilities def sine = x -> Math.sin(x) println sine(0.123) def cube = x -> x**3 println cube(0.987) sineCube = sine >> cube println sineCube(1.22) def sineDebug = x -> [Math.sin(x), 'sine was called.'] println sineDebug(0.3218) def cubeDebug = x -> [x**3, 'cube was called.'] println cubeDebug(3) def badSineCubeDebug = sineDebug << cubeDebug //badSineCubeDebug(3) // BOOM! def composeDebuggable = { f, g -> { x -> def (y, s) = g(x) def (z, t) = f(y) [z, s + t] } } println composeDebuggable(sineDebug, cubeDebug)(3) def bind = { f -> { g, h -> def (x, s) = [g, h] def (y, t) = f(x) [y, s + t] } } def sineCubeDebug = bind(sineDebug) << bind(cubeDebug) println sineCubeDebug(3, '') def lift = f -> unit << f roundDebug = lift(round) println roundDebug(0.9) def f = bind(lift(round)) << bind(sineDebug) println f(unit(27))
  110. Monadic style intro © Paul King 2006-2018 def unit =

    x -> [x, ''] println sineCubeDebug(unit(3)) println sineCubeDebug << unit << 3 def round = x -> Math.round(x) println round(1.23) def roundDebug = x -> unit(round(x)) println roundDebug << 1.23 // inspired by: // http://blog.klipse.tech/javascript/2016/08/31/monads-javascript.html Current example has been abbreviated to not really require Monadic capabilities def sine = x -> Math.sin(x) println sine(0.123) def cube = x -> x**3 println cube(0.987) sineCube = sine >> cube println sineCube(1.22) def sineDebug = x -> [Math.sin(x), 'sine was called.'] println sineDebug(0.3218) def cubeDebug = x -> [x**3, 'cube was called.'] println cubeDebug(3) def badSineCubeDebug = sineDebug << cubeDebug //badSineCubeDebug(3) // BOOM! def composeDebuggable = { f, g -> { x -> def (y, s) = g(x) def (z, t) = f(y) [z, s + t] } } println composeDebuggable(sineDebug, cubeDebug)(3) def bind = { f -> { g, h -> def (x, s) = [g, h] def (y, t) = f(x) [y, s + t] } } def sineCubeDebug = bind(sineDebug) << bind(cubeDebug) println sineCubeDebug(3, '') def lift = f -> unit << f roundDebug = lift(round) println roundDebug(0.9) def f = bind(lift(round)) << bind(sineDebug) println f(unit(27))
  111. Monadic style intro © Paul King 2006-2018 def unit =

    x -> [x, ''] println sineCubeDebug(unit(3)) println sineCubeDebug << unit << 3 def round = x -> Math.round(x) println round(1.23) def roundDebug = x -> unit(round(x)) println roundDebug << 1.23 // inspired by: // http://blog.klipse.tech/javascript/2016/08/31/monads-javascript.html Current example has been abbreviated to not really require Monadic capabilities def sine = x -> Math.sin(x) println sine(0.123) def cube = x -> x**3 println cube(0.987) sineCube = sine >> cube println sineCube(1.22) def sineDebug = x -> [Math.sin(x), 'sine was called.'] println sineDebug(0.3218) def cubeDebug = x -> [x**3, 'cube was called.'] println cubeDebug(3) def badSineCubeDebug = sineDebug << cubeDebug //badSineCubeDebug(3) // BOOM! def composeDebuggable = { f, g -> { x -> def (y, s) = g(x) def (z, t) = f(y) [z, s + t] } } println composeDebuggable(sineDebug, cubeDebug)(3) def bind = { f -> { g, h -> def (x, s) = [g, h] def (y, t) = f(x) [y, s + t] } } def sineCubeDebug = bind(sineDebug) << bind(cubeDebug) println sineCubeDebug(3, '') def lift = f -> unit << f roundDebug = lift(round) println roundDebug(0.9) def f = bind(lift(round)) << bind(sineDebug) println f(unit(27))
  112. Monadic style intro © Paul King 2006-2018 def unit =

    x -> [x, ''] println sineCubeDebug(unit(3)) println sineCubeDebug << unit << 3 def round = x -> Math.round(x) println round(1.23) def roundDebug = x -> unit(round(x)) println roundDebug << 1.23 // inspired by: // http://blog.klipse.tech/javascript/2016/08/31/monads-javascript.html Current example has been abbreviated to not really require Monadic capabilities def sine = x -> Math.sin(x) println sine(0.123) def cube = x -> x**3 println cube(0.987) sineCube = sine >> cube println sineCube(1.22) def sineDebug = x -> [Math.sin(x), 'sine was called.'] println sineDebug(0.3218) def cubeDebug = x -> [x**3, 'cube was called.'] println cubeDebug(3) def badSineCubeDebug = sineDebug << cubeDebug //badSineCubeDebug(3) // BOOM! def composeDebuggable = { f, g -> { x -> def (y, s) = g(x) def (z, t) = f(y) [z, s + t] } } println composeDebuggable(sineDebug, cubeDebug)(3) def bind = { f -> { g, h -> def (x, s) = [g, h] def (y, t) = f(x) [y, s + t] } } def sineCubeDebug = bind(sineDebug) << bind(cubeDebug) println sineCubeDebug(3, '') def lift = f -> unit << f roundDebug = lift(round) println roundDebug(0.9) def f = bind(lift(round)) << bind(sineDebug) println f(unit(27))
  113. Monadic style intro © Paul King 2006-2018 def unit =

    x -> [x, ''] println sineCubeDebug(unit(3)) println sineCubeDebug << unit << 3 def round = x -> Math.round(x) println round(1.23) def roundDebug = x -> unit(round(x)) println roundDebug << 1.23 // inspired by: // http://blog.klipse.tech/javascript/2016/08/31/monads-javascript.html Current example has been abbreviated to not really require Monadic capabilities def sine = x -> Math.sin(x) println sine(0.123) def cube = x -> x**3 println cube(0.987) sineCube = sine >> cube println sineCube(1.22) def sineDebug = x -> [Math.sin(x), 'sine was called.'] println sineDebug(0.3218) def cubeDebug = x -> [x**3, 'cube was called.'] println cubeDebug(3) def badSineCubeDebug = sineDebug << cubeDebug //badSineCubeDebug(3) // BOOM! def composeDebuggable = { f, g -> { x -> def (y, s) = g(x) def (z, t) = f(y) [z, s + t] } } println composeDebuggable(sineDebug, cubeDebug)(3) def bind = { f -> { g, h -> def (x, s) = [g, h] def (y, t) = f(x) [y, s + t] } } def sineCubeDebug = bind(sineDebug) << bind(cubeDebug) println sineCubeDebug(3, '') def lift = f -> unit << f roundDebug = lift(round) println roundDebug(0.9) def f = bind(lift(round)) << bind(sineDebug) println f(unit(27))
  114. 158 objectcomputing.com © 2018, Object Computing, Inc. (OCI). All rights

    reserved. Top 10 links https://github.com/paulk-asert/functional-groovy https://speakerdeck.com/paulk/functional-groovy https://github.com/mperry/functionalgroovy http://mariogarcia.github.io/functional-groovy/ http://mariogarcia.github.io/fnz http://www.vavr.io/ https://github.com/pure4j/pure4j https://www.ibm.com/developerworks/library/j-java8idioms1/ http://www.baeldung.com/java-8-functional-interfaces https://github.com/Frege/frege