Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ......

40
Modul- Fortgeschrittene Programmierkonzepte Bachelor Informatik 12- Functional Programming Prof. Dr. Marcel Tilly Fakultät für Informatik, Cloud Computing © Technische Hochschule Rosenheim

Transcript of Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ......

Page 1: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

Modul- FortgeschritteneProgrammierkonzepte

Bachelor Informatik

12- Functional Programming

Prof. Dr. Marcel Tilly

Fakultät für Informatik, Cloud Computing

© Technische Hochschule Rosenheim

Page 2: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

Roboocode

1st Robocode X-Mas Cup

2 / 40© Technische Hochschule Rosenheim

Page 3: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 4: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

Today in 'Übung 3'

No requests so far!

4 / 40© Technische Hochschule Rosenheim

Page 5: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

Agenda for today!

Introduction into FunctionalProgramming

5 / 40© Technische Hochschule Rosenheim

Page 6: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 7: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 8: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 9: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 10: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 11: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 12: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 13: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

FunctionalProgrammingWhat the F*!!!

13 / 40© Technische Hochschule Rosenheim

Page 14: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 15: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 16: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 17: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 18: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 19: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 20: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 21: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 22: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 23: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 24: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

RecursionBitte nicht zu lange auf diese Slide schauen!!!

24 / 40© Technische Hochschule Rosenheim

Page 25: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

RecursionBitte nicht zu lange auf diese Slide schauen!!!

25 / 40© Technische Hochschule Rosenheim

Page 26: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 27: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 28: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 29: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 30: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 31: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 32: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 33: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

�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

Page 34: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 35: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 36: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 37: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 38: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

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

Page 39: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

Questions!

39 / 40© Technische Hochschule Rosenheim

Page 40: Programmierkonzepte Modul- FortgeschritteneGenerics Design Pattern Threads Functional Interfaces ... java.lang.String no methods to change instance always returns new instance final

Final Thought!

40 / 40© Technische Hochschule Rosenheim