Hold the Lambdas! – Functional Programming in Java Today – What are functions?



Hold the Lambdas! – Functional Programming in Java Today – What are functions?

0 0


hold-the-lambdas

DevIgnition presentation

On Github wsh / hold-the-lambdas

Hold the Lambdas!

Functional Programming in Java Today

Will Hayworth / @_wsh / Lab49

DevIgnition / December 6, 2013

What's functional programming?

What are functions?

  • Functions consist of operations and any number of free variables to be used in them.
  • Application requires resolving those variables.
  • Computation is function application.
sum :: Num a => a -> a -> a
sum x y = x + y

sum 2 3
def sum(x: Int)(y: Int): Int = {
    x + y
}

sum(2)(3)
public int sum(int a, int b){
    return a + b;
}

sum(2, 3);

What's functional programming?

Is it...

  • First-class functions? Lambda syntax?
  • Not having side effects? Monads?
  • Whatever Hacker News wants it to be?

It's a way of thinking that emphasizes

  • Immutability
  • Really separating your concerns

Immutability

public static int sumOfSquares(int[] input) {
    int sum = 0;
    for(int i: input){
        sum += i * i;
    }
    return sum;
}
def sumOfSquares(xs: Array[Int]): Int = {
    var sum = 0
    for(i <- xs){
      sum += i * i
    }
    sum
}
def sumOfSquares(xs: Array[Int]): Int = {
    xs.foldLeft(0)((sum, x) => sum + (x * x))
}
sumOfSquares :: Num a => [a] -> a
sumOfSquares xs = foldl (\sum x -> sum + (x * x)) 0 xs
def sumOfSquares(xs: Array[Int]): Int = {
    xs.map(x => x * x).reduceLeft((sum, x) => sum + x)
}
sumOfSquares :: Num a => [a] -> a
sumOfSquares xs = foldl1 (\sum x -> sum + x) (map (\x -> x * x) xs)

Functions in Java

  • Guava
  • Apache Commons
public static int sumSquares(final Integer[] input) {
    final Collection<Integer> squared =
            Collections2.transform(Arrays.asList(input),
                                   new Function<Integer, Integer>() {
        public Integer apply(Integer integer) {
            return integer * integer;
        }
    });
    int sum = 0;
    for(int i: squared){
        sum += i;
    }
    return sum;
}
These [functional idioms] are by far the most easily (and most commonly) abused parts of Guava, and when you go to preposterous lengths to make your code "a one-liner," the Guava team weeps.

(Probably more) safety

public static int sumSquares(int[] input) {
    for(int i=0; i<input.length; i++){
        input[i] = input[i] * input[i];
    }
    int sum = 0;
    for(int i: input){
        sum += i;
    }
    return sum;
}

(Possibly better) performance

Real separation of concerns

public static int sumSquares(final Collection<Integer> input) {
    final Collection<Integer> squared =
            Collections2.transform(input,
                                   new Function<Integer, Integer>() {
        public Integer apply(Integer integer) {
            return integer * integer;
        }
    });
    int sum = 0;
    for(int i: squared){
        sum += i;
    }
    return sum;
}
public static <T> int mapThenSum(final Collection<T> input,
                                 final Function<T, Integer> processor) {
    final Collection<Integer> processed =
            Collections2.transform(input, processor);
    int sum = 0;
    for(int i: processed){
        sum += i;
    }
    return sum;
}
public static <T, U, V> V mapReduce(final Collection<T> input,
                                    final Function<T, U> mapper,
                                    final Function<V, Function<U, V>> reducer,
                                    final V initial) {
    final Collection<U> mapped =
            Collections2.transform(input, mapper);
    V result = initial;
    for(U value : mapped){
        final Function<U, V> toApply = reducer.apply(result);
        result = toApply.apply(value);
    }
    return result;
}
mapReduce(Lists.newArrayList(1, 2), 
        new Function<Integer, Integer>() {
            public Integer apply(final Integer integer) {
                return integer * integer;
            }
        }, new Function<Integer, Function<Integer, Integer>>() {
            public Function<Integer, Integer> apply(final Integer a) {
                return new Function<Integer, Integer>() {
                    public Integer apply(final Integer b) {
                        return a + b;
                    }
                };
            };
        }, 0));
public static <T, U, V> V mapReduce(final Iterator<T> input, 
                                    final Function<T, U> mapper,
                                    final Function<V, Function<U, V>> reducer,
                                    final V initial) {
    if (!input.hasNext()) {
        return initial;
    } else {
        final V next = reducer.apply(initial).apply(mapper.apply(input.next()));
        return reduce(input, mapper, reducer, next);
    }
}
mapReduce(Iterators.forArray(1, 2), 
        new Function<Integer, Integer>() {
            public Integer apply(final Integer integer) {
                return integer * integer;
            }
        }, new Function<Integer, Function<Integer, Integer>>() {
            public Function<Integer, Integer> apply(final Integer a) {
                return new Function<Integer, Integer>() {
                    public Integer apply(final Integer b) {
                        return a + b;
                    }
                };
            };
        }, 0));

You already do this.

Executors.newSingleThreadExecutor().submit(new Runnable() {
    public void run() {
        // ...
    }
});
Collections.sort(collection, new Comparator<T>() {
    public int compare(T o1, T o2) {
        // ...
    }
});

Dependency injection for behavior

Collection<String> presenters =
    Lists.newArrayList("Gray", "Michael", "Caroline", "Will");
Predicate<String> query = Predicates.or(
                                 new Predicate<String>() {
                                     public boolean apply(String o) {
                                         return o.startsWith("M");
                                     }
                                 }, new Predicate<String>() {
                                     public boolean apply(String o) {
                                         return o.startsWith("C");
                                     }
                                 });
Collections2.filter(presenters, query); // { "Michael", "Caroline" }
Collection<String> presenters =
    Lists.newArrayList("Gray", "Michael", "Caroline", "Will");
final Function<String, String> capitalizeAndReverse =
        Functions.compose(new Function<String, String>() {
                              public String apply(final String s) {
                                  return s.toUpperCase();
                              }
                          }, new Function<String, String>() {
                              public String apply(final String s) {
                                  final char[] chars = s.toCharArray();
                                  for(int i = 0, j = chars.length - 1;
                                      i < chars.length / 2; i++, j--) {
                                      char temp = chars[i];
                                      chars[i] = chars[j];
                                      chars[j] = temp;
                                  }
                                  return new String(chars);
                              }
                          }
        );
Collections2.transform(presenters, capitalizeAndReverse); // { "YARG", "LEAHCIM", "ENILORAC", "LLIW" }

Java 8

Everything until now has been Java 7 thanks to

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import java.util.function.Function;
import java.util.function.Predicate;

(not quite)

Java 8 gives us

  • Lambda syntax
  • Streams
  • Related goodness like method references
final List<String> presenters =
    Arrays.asList("Gray", "Michael", "Caroline", "Will");
presenters.stream()
       .filter(s -> s.startsWith("G") || s.startsWith("M"))
       .forEach(s -> System.out.println(s));

(yes, that's real Java)

final List<String> presenters =
    Arrays.asList("Gray", "Michael", "Caroline", "Will");
presenters.stream()
       .filter(s -> s.startsWith("G") || s.startsWith("M"))
       .forEach(System.out::println);

(method reference)

How does this work under the hood?

  • Stream
  • @FunctionalInterface

Streams support

  • Intermediate operations
    • map
    • flatMap
    • filter
  • Terminal operations
    • reduce
    • forEach
    • findFirst
final List<String> strings = Arrays.asList("Gray", "Michael", "Caroline", "Will");
strings.stream()
       .filter(s -> s.startsWith("G") || s.startsWith("M"))
       .forEach(System.out::println);
strings.stream()
       .allMatch(s -> s.length() > 3); // true
strings.stream()
       .count();                       // 4
strings.stream()
       .map(String::toUpperCase)
       .collect(Collectors.joining()); // GRAYMICHAELCAROLINEWILL
final List<String> strings = Arrays.asList("Gray", "Michael", "Caroline", "Will");
strings.parallelStream()
       .filter(s -> s.startsWith("G") || s.startsWith("M"))
       .forEach(System.out::println);
strings.parallelStream()
       .allMatch(s -> s.length() > 3); // true
strings.parallelStream()
       .count();                       // 4
strings.parallelStream()
       .map(String::toUpperCase)
       .collect(Collectors.joining()); // GRAYMICHAELCAROLINEWILL
package java.util.function;
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);  
    default <V> Function<V, R>
        compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    default <V> Function<T, V>
        andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
excerpt from java/util/function/Function.java
package scala

trait Function1[-T1, +R] extends AnyRef { self =>
  def apply(v1: T1): R

  def compose[A](g: A => T1): A => R = { x => apply(g(x)) }

  def andThen[A](g: R => A): T1 => A = { x => g(apply(x)) }
}
excerpt from scala/Function1.scala

One other useful thing:

Optional

scala> val x = List((1,2)).toMap
x: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2)
scala> x.get(1)
res2: Option[Int] = Some(2)
scala> x.get(3)
res3: Option[Int] = None
scala> x.getOrElse(4, 5)
res4: Option[Int] = 5
Prelude> import qualified Data.Map.Lazy as M
Prelude M> let x = M.fromList[(1,2)]
Prelude M> M.lookup 1 x
Just 2
Prelude M> M.lookup 3 x
Nothing
Prelude M> M.findWithDefault 5 4 x
5

What does null mean?

HashMap<Integer, Integer> someHashMap = new HashMap<>();
someHashMap.put(1, 2);
Optional<Integer> foo = Optional.fromNullable(someHashMap.get(1));
Optional<Integer> bar = Optional.fromNullable(someHashMap.get(3));

if (foo.isPresent()) foo.get(); // 2
bar.isPresent(); // false
bar.get(); // IllegalStateException
bar.or(5); // 5

...in Guava and in Java 8.

import com.google.common.base.Optional;
/* or */
import java.util.Optional;

In short

  • Java 8's lambda syntax doesn't change that much.

In short

  • Java 8's lambda syntax doesn't change that much.
  • Java 8 does more to harness the underlying power that FP-style programs provide with constructs like streams.
  • You can harness many of the benefits of functional thinking in Java today if you try for immutability, minimal state, and vigorous separation of concerns.

Thanks!

@_wsh

will.hayworth@lab49.com