Turning Code into Data
René Schwietzke, Xceptance
Functional programming (fP) uses functions to achieve its goals. Compared to imperative or object-oriented programming where the use of control statements prevails.
What?
Seriously what is Functional Programming (fP)?
Better?
For fans of the pure theory.
Good OO programming implicitly used a lot of fP concepts already.
What do we really want?
public void classic()
{
// get me old Apples sorted by age
List<SKU> apples = new ArrayList<>();
for (SKU s : inventory)
{
if ("Apple".equals(s.getBrand()) && s.getAge() > 2)
{
apples.add(s);
}
}
Collections.sort(apples, new Comparator<SKU>() {
@Override
public int compare(SKU s1, SKU s2) {
return s1.getAge() > s2.getAge() ? 1 : -1;
}
};
);
}
public void fP()
{
// get me old Apples
final List<SKU> apples = inventory
.filter(
s -> "Apple".equals(s.getBrand()) && s.getAge() > 2
)
.sort(
(s, t) -> s.getAge() > t.getAge() ? 1 : -1
);
}
What might be functional?
Let's see if we can see what functional really is
public void functional(String s, List<String> l)
{
final int length = s.length();
final String s2 = s.substring(3);
l.add(s);
Collections.shuffle(l);
final int i = Random.nextInt(l.size());
final String s3 = l.get(i);
final List<String> l2 = l.subList(0, l.size() / 2);
}
public void functional(String s, List<String> l)
{
final int length = s.length(); // yes
final String s2 = s.substring(3); // yes
l.add(s); // no
Collections.shuffle(l); // very no
final int i = Random.nextInt(l.size()); // never
final String s3 = l.get(i); // yes
final List<String> l2 = l.subList(0, l.size() / 2); // yes and no
}
Functional is not easy to understand and hardcore functional even questions assignments.
Let's try something
Let's come up with some example to demonstrate a few nifty things.
/**
* The classic approach I
*/
public List<Vehicle> filterByBrand(final List<Vehicle> vehicles)
{
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if ("Maybach".equals(v.getBrand())
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final List<Vehicle> vehicles = getAllVehicles();
final List<Vehicle> filtered = filterByBrand(vehicles);
}
Kind of simple, isn't it?
Is this functional?
/**
* The classic approach I
*/
public List<Vehicle> filterByBrand(final List<Vehicle> vehicles)
{
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if ("Maybach".equals(v.getBrand())
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final List<Vehicle> vehicles = getAllVehicles();
final List<Vehicle> filtered = filterByBrand(vehicles);
}
filterByBrand
is functional without side-effects
someOtherCode
is functional too
Avoid hardcoding anything ever.
/**
* The classic approach II with flexible brand
*/
public List<Vehicle> filterByBrand(
final List<Vehicle> vehicles, final String brand)
{
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if (brand.equals(v.getBrand())
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final List<Vehicle> vehicles = getAllVehicles();
final List<Vehicle> filtered = filterByBrand(vehicles, "Maybach");
}
Naive solution of multi-brand filtering
/**
* The classic approach II with flexible brand
*/
public List<Vehicle> filterByBrand(
final List<Vehicle> vehicles, final Set<String> brands)
{
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if (brands.contains(v.getBrand())
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final List<Vehicle> vehicles = getAllVehicles();
final Set<String> brandsToFilter = new HashSet<>();
brandsToFilter.add("Maybach");
brandsToFilter.add("Audi"),
final List<Vehicle> filtered = filterByBrand(vehicles, brandsToFilter);
}
Just to get rid off boilerplate
/**
* The classic approach III less boilerplate code
*/
public List<Vehicle> filterByBrand(
final List<Vehicle> vehicles, final String... brands)
{
final Set<String> brandsToFilter = new HashSet<>();
for (String brand : brands)
{
brandsToFilter.add(brand);
}
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if (brandsToFilter.contains(v.getBrand())
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final List<Vehicle> vehicles = getAllVehicles();
final List<Vehicle> filtered =
filterByBrand(vehicles, "Audi", "Maybach");
}
public interface Filter<T>
{
public boolean matches(T t);
}
/**
* The classic approach IV with anonymous classes
*/
public List<Vehicle> filterByBrand(
final List<Vehicle> vehicles,
final Filter<Vehicle> brandFilter)
{
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if (brandFilter.matches(v))
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final List<Vehicle> vehicles = getAllVehicles();
final List<Vehicle> filtered =
filterByBrand(vehicles,
new Filter<Vehicle>()
{
public boolean matches(Vehicle v)
{
return "Maybach".equals(v.getBrand()) && v.getAge() > 20;
}
}
);
}
The nice Java 7 solution
Finally... I thought he won't stop
Time to save us
Vehicle
as input and Boolean
as output
Function<String, Boolean>
/**
* Step One - Use Java 8 Functions
*/
public List<Vehicle> filterByBrand(
final List<Vehicle> vehicles,
final Function<Vehicle, Boolean> filter)
{
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if (filter.apply(v))
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final Function<Vehicle, Boolean> f =
v -> "Maybach".equals(v.getBrand());
final List<Vehicle> filtered = filterByBrand(vehicles, f);
}
public void someOtherCode()
{
final List<Vehicle> filtered = filterByBrand(vehicles,
v -> "Maybach".equals(v.getBrand());
}
Not bad
What just happened?
public void someOtherCode()
{
final List<Vehicle> filtered1 =
filterByBrand(
vehicles,
v -> "Maybach".equals(v.getBrand()));
final List<Vehicle> filtered2 =
filterByBrand(
vehicles,
v -> "Maybach".equals(v.getBrand()) ||
"Audi".equals(v.getBrand()));
final List<Vehicle> filtered3 =
filterByBrand(
vehicles,
v -> "Maybach".equals(v.getBrand()) &&
v.getAge() > 30));
}
Vehicle
and return Boolean
v -> true
works too
Function<T, R>
is a @FunctionalInterface
offered by Java 8
Wow!
What the compiler needs to understand us
@FunctionalInterface
is not required, but meant to aid documentation
and usage concepts
@FunctionalInterface
public interface Function<T, R>
{
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
public interface Filter<T>
{
/**
* Reminder: This is what we invented for Java 7!
*/
boolean matches(T t);
}
@FunctionalInterface
public interface Foobar<T, K, V, Y>
{
/**
* My very own interface
*/
Y dance(T t, K k, V v);
}
Java 8 delivers 40+ functional interfaces
java.util.function
// Represents a supplier of results
Supplier<T> {T get();}
// Represents a predicate (boolean-valued function) of one argument
Predicate<T> {boolean test(T t);}
// Represents an operation that accepts a single input argument and
// returns no result. Unlike most other functional interfaces, Consumer
// is expected to operate via side-effects.
Consumer<T> {void accept(T t);}
// Represents a function that accepts one argument and produces a result
Function<T, R> {R apply(T t);}
// Represents a function that accepts two arguments and produces a result.
BiFunction<T, U, R> {R apply(T t, U u);}
// and about 40 more...
https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
Java offers a ready version of Function<T, S> - Predicate<T>
/**
* Step One - Use Java 8 Functions
*/
public List<Vehicle> filterByBrand(
final List<Vehicle> vehicles,
final Function<Vehicle, Boolean> filter)
{
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if (filter.apply(v))
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final List<Vehicle> filtered2 =
filterByBrand(
vehicles,
v -> "Maybach".equals(v.getBrand()));
}
/**
* Step One and A Half - Use Java 8 Predicate
*/
public List<Vehicle> filterByBrand(
final List<Vehicle> vehicles,
final Predicate<Vehicle> filter)
{
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if (filter.test(v))
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final List<Vehicle> filtered2 =
filterByBrand(
vehicles,
v -> "Maybach".equals(v.getBrand()));
}
Cool...
Key thoughts at a glance
Predicate
expects one parameter T and returns a boolean
test(T t)
test
{}
, the result of the evaluation is the return value
public void foo(final Predicate<String> p)
{
final String s = "foobar";
if (p.test(s))
{
// whatever
}
}
public void something()
{
foo(s -> s.startsWith("A"));
}
public void something()
{
foo(s -> {
boolean r = s.startsWith("AA");
return r;
});
}
Fancy, right?
Just to prove that it all existed before
public void foo(final Predicate<String> p)
{
final String s = "foobar";
if (p.test(s))
{
// whatever
}
}
public void something()
{
foo(s -> s.startsWith("A"));
}
public void foo(final Predicate<String> p)
{
final String s = "foobar";
if (p.test(s))
{
// whatever
}
}
public void something()
{
foo(
new Predicate<String>()
{
public boolean test(final String s)
{
return s.startsWith("A");
}
}
);
}
Nothing else than what we did with Java 7... no boilerplate though.
More examples
public void something()
{
// more code but once you use {} it requires a return value
// if the Function requires one
foo(s -> {
final String t = s + s;
return t.isEmpty()
});
// Predicate has several pre-implemented methods
final Predicate<String> p = s -> true;
foo(p.negate());
final Predicate<String> p1 = s -> true;
final Predicate<String> p2 = s -> s.isEmpty();
foo(p1.and(p2));
foo(p1.or(p2));
// a plain method references... more next
foo(String::isEmpty);
}
More questions
public void foo(Predicate<String> s)
{
// stuff here
}
public void something()
{
/* 1 */ foo(s -> true);
/* 2 */ foo(t -> t.substring(3, 4));
/* 3 */ foo(water -> water.length() > 0);
/* 4 */ foo(s -> { return true; });
// 5
String k = "Hello";
foo(s -> k.length() > 0);
// 6
boolean result = false;
foo(s -> { result = s.isEmpty; return result; });
// 7
boolean temp = true;
foo(s -> s.isEmpty() && temp);
// 8
boolean temp2 = true;
foo(s -> s.isEmpty() && temp2);
temp2 = false;
}
1 - ok; 2 - wrong, no boolean result; 3 - ok; 4 - identical to 1; 5 - ok; 6 - wrong, result is not final; 7 - ok, we don't modify temp; 8 - wrong, temp2 is not final or effectively final
When we need more or less parameters
public interface MyFunction<A, B, C, D>
{
int evaluate(A a, B b, C c, D d);
}
public void foo(MyFunction<String, String, String, Integer>)
{
final int i = f.evaluate("AB", "B", "Foo", 42);
}
public void something()
{
foo((a, b, c, o) -> {
final String t = a + c.replaceAll(a, b);
return t.length() + o;
});
}
public void foo(Supplier<String> s)
{
// a supplier creates data, just a return value
final String a = p.get();
}
public void bar(Consumer<Integer> c)
{
// a consumer "eats" data
p.accept(1);
}
public void something()
{
foo(() -> "A"); // no data as input
bar(c -> System.out.print(c)); // no return value!
bar(System.out::print);
}
You can do everything you can do with methods.
When you are tired of typing
s -> s.isEmpty()
for our Predicate<String>
String::isEmpty
String::new
if this would be just your method call
public void foo1(Predicate<String> p) {...}
public void foo2(BiFunction<String, String, Integer> p) {...}
public void foo3(BiFunction<String, String, String> p) {...}
public void something()
{
foo1(s -> s.isEmpty());
foo1(String::isEmpty);
foo1(s -> Boolean.getBoolean(s));
foo1(Boolean::getBoolean); // static method!
foo1(s -> s.equalsIgnoreCase("A"));
// foo1(String::equalsIgnoreCase("A")); // wont't work!!
foo2((s, t) -> s.indexOf(t));
foo2(String::indexOf);
foo2((s, t) -> s.compareTo(t));
foo2(String::compareTo);
foo2((s, t) -> t.compareTo(s)); // that is not the same!!!
foo3((s, t) -> s.replaceAll(t, t));
// foo3(String::replaceAll); // won't work
foo3((s, t) -> String.format(s, t));
foo3(String::format); // static again!
}
Method references are tricky!
The Ultimate Showdown
public List<Vehicle> filterByBrand(
final List<Vehicle> vehicles,
final Filter<Vehicle> brandFilter)
{
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if (brandFilter.matches(v))
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final List<Vehicle> vehicles = getAllVehicles();
final List<Vehicle> filtered =
filterByBrand(vehicles,
new Filter<Vehicle>()
{
public boolean matches(Vehicle v)
{
return "Maybach".equals(v.getBrand()) && v.getAge() > 20;
}
}
);
}
public interface Filter<T>
{
public boolean matches(T t);
}
public List<Vehicle> filterByBrand(
final List<Vehicle> vehicles,
final Predicate<Vehicle> filter)
{
final List<Vehicle> result = new ArrayList<>();
for (Vehicle v : vehicles)
{
if (filter.test(v))
{
result.add(v);
}
}
return result;
}
public void someOtherCode()
{
final List<Vehicle> vehicles = getAllVehicles();
final List<Vehicle> filtered =
filterByBrand(vehicles,
v -> "Maybach".equals(v.getBrand()) && v.getAge() > 20
);
}
That is impressive, but wait, there is more!