Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ......
Transcript of Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ......
Modul- FortgeschritteneProgrammierkonzepte
Bachelor Informatik
12- Functional Programming
Prof. Dr. Marcel Tilly
Fakultät für Informatik, Cloud Computing
© Technische Hochschule Rosenheim
Roboocode
1st Robocode X-Mas Cup
2 / 40© Technische Hochschule Rosenheim
Probeklausur
Zur Vorbereitung:Klausur im Learning Campus
Allgemeine FragenGenericsDesign PatternThreadsFunctional InterfacesFunctional ProgrammingGIT
Besprechung am 15.1.2020 in Übung 3 (13:15!)
3 / 40© Technische Hochschule Rosenheim
Today in 'Übung 3'
No requests so far!
4 / 40© Technische Hochschule Rosenheim
Agenda for today!
Introduction into FunctionalProgramming
5 / 40© Technische Hochschule Rosenheim
FunctionalProgrammingFunctional Programming (FP) is a programming paradigm that (re-)gained quite sometraction in the past years.
"Functional programming is a style of programming that emphasizes the evaluation ofexpressions, rather ten execution of commands. The expression in these languages areformed by using functions to combine basic values" - Graham Hutton, 2002
Core Elememts of FP are:
Purity: The pure function will return the exact result every time, and it doesn’tmutate any data outside of it.Immutability: In functional programming, x = x + 1 is illegal.Higher-Order Functions: Functions are treated as objects, therefore we can passfunctions around as we would any other value. A higher order function is simply afunction that operates on other functions
6 / 40© Technische Hochschule Rosenheim
FP LanguagesPopuluar functional programming languages these days are:
Scalacombines object-oriented and functional aspects; executed on the Java VMand can thus integrate seamlessly with any existing Java libraries.
JavaScriptnot Java!untyped, functional programming
Pythonobject-oriented and functional
Erlang/Elixirfunctional and executed in Erlang VM
Haskellpure functional and typed
Others: LISP, Closure, Elm, F#
7 / 40© Technische Hochschule Rosenheim
Detour: ScalaScala syntax:
It follows the substitution principle, where the result of the last instruction is thereturn value.It has built-in operators for list operations (head, tail, add, split, etc.)
With Scala, insertion sort can be written in just a few lines of code:
// to sort a list...def isort(xs: List[Int]): List[Int] = xs match { // an empty list is sorted case Nil => Nil // a list with a single element is also sorted case List(x) => List(x) // otherwise, cut off the first element (y) and // insert it into the sorted remaining list (ys) case y :: ys => insert(isort(ys), y)}
// to insert an element into a (sorted) list...def insert(xs: List[Int], x: Int): List[Int] = xs match { // if the list was empty, return a new list with just x case Nil => List(x) // otherwise: cut off the first element of xs and ... case y :: ys => if (x < y) x :: xs // prepend x to xs else y :: insert(ys, x) // insert x into ys}
8 / 40© Technische Hochschule Rosenheim
Detour: HaskellHaskell syntax:
pure, clean, small.Natural built-in operators for list operations (head, tail, add, split, etc.)Compiles to binary
With Haskell, insertion sort can be written in even fewer lines of code:
insert :: Ord a => a -> [a] -> [a]insert x [] = [x]insert x (y:ys) | x < y = x:y:ys | otherwise = y:(insert x ys)
isort :: Ord a => [a] -> [a]isort [] = []isort (x:xs) = insert x (isort xs)
9 / 40© Technische Hochschule Rosenheim
Detour: ElixirElixir syntax:
With scope and module.Natural built-in operators for list operations (head, tail, add, split, etc.)
With Elixir, insertion sort is still small:
defmodule Sort do def isort(list) when is_list(list), do: isort(list, []) def isort([], sorted), do: sorted def isort([h | t], sorted), do: isort(t, insert(h, sorted))
defp insert(x, []), do: [x] defp insert(x, sorted) when x < hd(sorted), do: [x | sorted] defp insert(x, [h | t]), do: [h | insert(x, t)]end
10 / 40© Technische Hochschule Rosenheim
Reminder: JavaIn Java still clean but hard to get what the code is doing!
public static void insertSort(int[] A){ for(int i = 1; i < A.length; i++){ int value = A[i]; int j = i - 1; while(j >= 0 && A[j] > value){ A[j + 1] = A[j]; j = j - 1; } A[j + 1] = value; }}
11 / 40© Technische Hochschule Rosenheim
Text BooksFunctional Programming in Java by Venkat Subramaniam.
Parallel and Concurrent Programming in Haskell by Simon Marlow
Programming Erlang by Joe Armstrong
Pearls of Functional Algorithm Design by Richard Bird
12 / 40© Technische Hochschule Rosenheim
FunctionalProgrammingWhat the F*!!!
13 / 40© Technische Hochschule Rosenheim
FP - PurityPure functions return a value solely based on what was passed into it, it doesn’t modifyvalues outside of its scope, that makes it independent from any state in the system.
Pure functions operate on their input parameters.Pure functions never mutate data.Pure functions have no side effects.Pure functions will always produce the same output given the same inputs.
function justTen() { return 10;}
function square(x) { return x * x;}
function add(x, y) { return x + y;}
14 / 40© Technische Hochschule Rosenheim
FP - ImpurityImpure functions can create side-effects.
Impure Function:
var tip = 0;
function calculateTip( mealTotal ) { tip = 0.15 * mealTotal;}
calculateTip( 150 )
NOTE: Unfortunatley you use impure functions in functional programming!!!
Question: Is object-orentation a functional programming paradigm!
15 / 40© Technische Hochschule Rosenheim
FP - ImmutableObjectsIf objects cannot be changed after their creation, parallelization becomes much easier.
java.lang.String
no methods to change instancealways returns new instance
final modi�er for attributes and variable, sort of:
only prevents overwriting of primitive type or referenceobject may still be mutated
No mutation means no for/while!
16 / 40© Technische Hochschule Rosenheim
But back to FP inJava.Functions as First-Class Citizens
@FunctionalInterfaceinterface Function<A, B> { B apply(A obj);}
Function<Integer, Integer> square = new Function<Integer, Integer>() { @Override public Integer apply(Integer i) { return i * i; }}
17 / 40© Technische Hochschule Rosenheim
Lambda in JavaOr shorter as lambda expression (arglist) -> { block; }
Function<Integer, Integer> square = (Integer i) -> { return i * i };
Or even shorter, for single instructions
Function<Integer, Integer> square = i -> i * i;
The types are usually automatically inferred.
For single instructions, you can omit the curly braces and return.
18 / 40© Technische Hochschule Rosenheim
Why FunctionalProgramming?So what's the big deal with functional programming?
1. Since objects are immutable, parallization is (almost) trivial (you may have heardof map-reduce).
2. Separation of Concerns (SoC): FP helps you to separate the data traversal (howyou iterate the data) from the business logic (what you do with the data).
19 / 40© Technische Hochschule Rosenheim
ExampleSay you want to
retrieve all students from a database,�lter out those who took Programmieren 3,load their transcript of records from another databaseprint all class names
Iterative Solution
for (Student s : getStudents()) { if (s.getClasses().contains("Programmieren 3")) { ToR tor = db.getToR(s.getMatrikel()); for (Record r : tor) { System.out.println(r.getName()); } }}
20 / 40© Technische Hochschule Rosenheim
A Simple ImmutableListhead stores the data, tail links to the next element.
The end of the list is explicitly modeled.
class List<T> { final T head; final List<T> tail;
private List(T el, List<T> tail) { this.head = el; this.tail = tail; }
boolean isEmpty() { return head == null; }
// ...}
21 / 40© Technische Hochschule Rosenheim
Some HelperFunctionsSome factory functions for convenience:
class List<T> { // ...
static <T> List<T> empty() { return new List<T>(null, null); }
static <T> List<T> list(T elem, List<T> xs) { return new List<>(elem, xs); }
static <T> List<T> list(T... elements) { if (elements.length == 0) return empty(); int i = elements.length - 1; List<T> xs = list(elements[i], empty()); while (--i >= 0) xs = list(elements[i], xs); return xs; }}
22 / 40© Technische Hochschule Rosenheim
UsageHere's an example usage:
import static List.empty;import static List.list;
List<Integer> sequence = list(1, 2, 3, 4, 5);List<Integer> emptyList = empty();List<Integer> prepend = list(0, empty());
System.out.println(sequence.isEmpty()); // "false"System.out.println(emptyList.isEmpty()); // "true"System.out.println(prepend.isEmpty()); // "false"
By now, you probably already realized the main issue with this class: once it'sinitialized, there is no way to change it. That means: all mutations on this list will haveto create a new list.
Even "worse": if variables can't be changed, there is no for/while iteration!
23 / 40© Technische Hochschule Rosenheim
RecursionBitte nicht zu lange auf diese Slide schauen!!!
24 / 40© Technische Hochschule Rosenheim
RecursionBitte nicht zu lange auf diese Slide schauen!!!
25 / 40© Technische Hochschule Rosenheim
Easy RecursionTo warm up, let's formulate a recursive toString() method for our List class.Remember: When writing recursive functions, you need to make sure to capture theterminal cases (the ones where you know the answer) and the recursion cases (theones where you make the recursive calls).
class List<T> { // ...
/** * Either 'nil' if the list is empty, or * '( head tail.toString )' otherwise */ @Override public String toString() { if (isEmpty()) return "nil"; else return "(" + head + " " + tail + ")"; }}
System.out.println(list(7, 3, 1, 3)); // "(7 (3 (1 (3 nil))))"
26 / 40© Technische Hochschule Rosenheim
Simple RecursionSimilarly, we can formulate a contains method that checks if a list contains anelement: Either the head matches, or it may be contained in tail.
static <T> boolean contains(List<T> xs, T obj) { if (xs.isEmpty()) return false; else if (xs.head.equals(obj)) return true; else return contains(xs.tail, obj);}
The same with the length of a list: An empty list has length zero, any other list is oneplus the length of its tail.
static <T> int length(List<T> xs) { if (xs.isEmpty()) return 0; else return 1 + length(xs.tail);}
27 / 40© Technische Hochschule Rosenheim
Recursion with ListGenerationThings become a bit more tricky if we want to mutate lists (or more precicely: get newlists which differ from the old ones).
For example, consider the take(int i) and drop(int i) functions that return alist with the �rst i or the sublist following the i-th element, respectively.
// recursion with list generationstatic <T> List<T> take(List<T> xs, int n) { if (n <= 0 || xs.isEmpty()) return empty(); else return list(xs.head, take(xs.tail, n-1));}
static <T> List<T> drop(List<T> xs, int n) { if (n <= 0 || xs.isEmpty()) return xs; else return drop(xs.tail, n-1);}
28 / 40© Technische Hochschule Rosenheim
Recursion for withList AppendingAppending to a list recursively is actually similar to the iterative way: if the target list isempty, the new list is the appendix; otherwise we make a new list where we keep thehead but append to the tail.
static <T> List<T> append(List<T> xs, List<T> y) { if (xs.isEmpty()) return y; else return list(xs.head, append(xs.tail, y));}
Since we know how to append to a list, list reversal becomes trivial: just make a newlist of the current head, and append that to the reversal of the tail.
static <T> List<T> reverse(List<T> xs) { if (xs.isEmpty()) return xs; else return append(reverse(xs.tail), list(xs.head, empty()));}
29 / 40© Technische Hochschule Rosenheim
Insertion SortThe idea of insertion sort is that inserting an element x into an already sorted list xstrivial: Skip all elements smaller then x before inserting.
In our immutable list scenario this means: "Copy" all values while smaller then x, theninsert x and append the remaining list.
The actual insertion sort method then just inserts the head into the sorted remaininglist.
static <T extends Comparable<T>> List<T> isort(List<T> xs) { if (xs.isEmpty()) return xs; else return insert(xs.head, isort(xs.tail));}
private static <T extends Comparable<T>> List<T> insert(T x, List<T> xs) { if (xs.isEmpty()) return list(x, empty()); else { if (x.compareTo(xs.head) < 0) return list(x, xs); else return list(xs.head, insert(x, xs.tail)); }}
30 / 40© Technische Hochschule Rosenheim
Merge SortMerge sort is a divide-and-conquer algorithm where the key idea is that merging twoalready sorted lists is trivial: Keep adding the smaller of both lists to your result listuntil all items have been added.
The actual merge sort method then (recursively) splits the input lists into halves untilthey only contain a single element or none at all -- those are already sorted.
static <T extends Comparable<T>> List<T> msort(List<T> xs) { if (xs.isEmpty()) return xs; // no element at all else if (xs.tail.isEmpty()) return xs; // only single element else { int n = length(xs); List<T> a = take(xs, n/2); List<T> b = drop(xs, n/2);
return merge(msort(a), msort(b)); }}
private static <T extends Comparable<T>> List<T> merge(List<T> xs, List<T> ys) { if (xs.isEmpty()) return ys; else if (ys.isEmpty()) return xs; else { if (xs.head.compareTo(ys.head) < 0) return list(xs.head, merge(xs.tail, ys)); else return list(ys.head, merge(xs, ys.tail)); }}
31 / 40© Technische Hochschule Rosenheim
Anonymous Classes,Lambda, Referencesstatic <A> void forEach(List<A> xs, Consumer<A> c) { if (xs.isEmpty()) return; else { c.accept(xs.head); forEach(xs.tail, c); }}
And here's a Consumer that prints elements to System.out:
List<Integer> xs = list(1, 2, 3, 4);forEach(xs, new Consumer<Integer>() { @Override public void accept(Integer i) { System.out.println(i); }});
// or shorter with lambdaforEach(xs, i -> System.out.println(i));
// or even shorter with method referencesforEach(xs, System.out::println);
32 / 40© Technische Hochschule Rosenheim
�lterA different yet very frequent use of lists is to �lter them by a particular predicate. Theresult of filter is a list that contains only elements that satisfy some condition.
Let's do this right away with a helper "function", a Predicate (link)
@FunctionalInterfaceinterface Predicate<T> { boolean test(T t);}
static <A> List<A> filter(List<A> xs, Predicate<A> p) { if (xs.isEmpty()) return xs; else if (p.test(xs.head)) return list(xs.head, filter(xs.tail, p_)); else return filter(xs.tail, p);}
List<Integer> xs = list(1, 2, 3, 4);List<Integer> lt3 = filter(xs, i -> i < 3);
33 / 40© Technische Hochschule Rosenheim
mapThe last functional concept for this class is map. When working with data, you oftenneed to transform one type of data into another. For example, you might retrieve a listof Student, but you actually need only a list of their family names.
That is: given a list of type Student, you want a list of type String.
static List<String> familyNames(List<Student> xs) { if (xs.isEmpty()) return empty(); else return list(xs.head.getFamilyName(), familyNames(xs.tail));}
Well, this seems fairly generic, doesn't it? You want to map one object to another, givensome function. Let's try this again, with the logic moved to a functional interface:
@FunctionalInterfaceinterface Function<A, B> { B apply(A a);}
static <A, B> List<B> map(List<A> xs, Function<A, B> f) { if (xs.isEmpty()) return empty(); else return list(f.apply(xs.head), map(xs.tail, f));}
List<Student> xs = ...;List<String> fns = map(xs, s -> s.getFamilyName());List<String> fns = map(xs, Student::getFamilyName); // even shorter
34 / 40© Technische Hochschule Rosenheim
FP in Java: StreamsSo far, we did all the exercises with a pretty useless list class. But filter, map andforEach are essential tools to process data.
In Java, these functional aspects are not attached (and thus limited) to lists, but to amore general concept of (possibly in�nite) data streams. This is more appropriate,since the data may originate from very different sources: web APIs, database resultsets, or plain text �les.
We'll talk more about Streams next week, but for now, please take note of thefollowing methods:
Stream<T>.filter(Predicate<? super T> p)Stream<T>.map(Function<? super T, ? extends R>)Stream<T>.forEach(Consumer<T> consumer)
In our examples above, the filter and map methods returned new lists. Here, theseintermediate methods return Streams. Our forEach method had return type void;here, this terminal operation also returns void.
35 / 40© Technische Hochschule Rosenheim
ExampleRecall the (iterative) example from the very top: retrieve a list of students, �nd thosewho attended a certain class, and then print out the names of the classes on theirtranscript of records.
Iterative Solution (see earlier slide)
for (Student s : Database.getStudents()) { if (s.getClasses().contains("Programmieren 3")) { Transcript tr = Database.getToR(s.getMatrikel()); for (Record r : tr) System.out.println(r); }}
36 / 40© Technische Hochschule Rosenheim
Example
Functional Solution
Database.getStudents().stream() .filter(s -> s.getClasses().contains("Programmieren 3")) .map(Student::getMatrikel) .map(Database::getToR) .flatMap(t -> t.records.stream()) // stream of lists to single list .forEach(System.out::println);
Isn't that much more precise as the nested for loops with if and method calls?
37 / 40© Technische Hochschule Rosenheim
Lazy EvaluationOne last word on ef�ciency.
The stream methods are lazy in a sense that the downstream operations are onlyapplied to the actual results of the previous steps.
To stick with the example above, the Student::getMatrikel would only beapplied to those who were passed on by filter.
In other words: the terminal operation (here: forEach) pulls data from the streams,all the way to the originating stream.
38 / 40© Technische Hochschule Rosenheim
Questions!
39 / 40© Technische Hochschule Rosenheim
Final Thought!
40 / 40© Technische Hochschule Rosenheim