functional programming ii

97
Functional Programming II ExperTalks 2015-09-25 Prashant Kalkar, Equal Experts Christian Hujer, Nelkinda Software Craft

Upload: prashant-kalkar

Post on 22-Jan-2018

329 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Functional programming ii

Functional Programming II

ExperTalks 2015-09-25Prashant Kalkar, Equal Experts

Christian Hujer, Nelkinda Software Craft

Page 2: Functional programming ii

Little more on side effects

Consider this example

val hitMonster = monster.hitByShooting(gunShot)

// reduces the power of gunShot

val hitPlayer = player.hitByShooting(gunShot)

Changes to the gunshot are happening on the slide, hence they are called as Side Effects.

Not referentially transparent.

Page 3: Functional programming ii

Functional way

val (hitMonster, usedGunShot) =

monster.hitByShooting(gunShot)

val hitPlayer = player.hitByShooting(usedGunShot)

Side effect is now visible in the return type.

No more side effect, just effect of the function.

New function is referentially transparent.

Page 4: Functional programming ii

Recursion

Page 5: Functional programming ii

Recursion

Page 6: Functional programming ii

recursion/riˈkərZHən/nounSee recursion.

Recursion

Page 7: Functional programming ii

recursion/riˈkərZHən/nounSee recursion. See also tail recursion.

Recursion

Page 8: Functional programming ii

recursion/riˈkərZHən/nounSee recursion. See also tail recursion.

tail recursion/tāl riˈkərZHən/nounIf you aren’t sick of it already, see tail recursion.

Recursion

Page 9: Functional programming ii

Recursion

Page 10: Functional programming ii

Recursion

“If you already know what recursion is, just remember the answer.Otherwise, find someone who is standing closer to Douglas Hofstadter than you are;then ask him or her what recursion is.”

‒ Andrew Plotkin

Page 11: Functional programming ii

A recursive law:Hofstadter’s Law

“It always takes longer than you expect, even when you take into account Hofstadter’s Law.”

‒ Douglas Hofstadter,Gödel, Escher, Bach:

An Eternal Golden Braid

Page 12: Functional programming ii

Recursive Acronyms

GNUGNU's Not Unix

WINEWINE Is Not an Emulator

PHPPHP Hypertext Preprocessor

Page 13: Functional programming ii

Recursion in Nature

Page 14: Functional programming ii

Recursion in Arts

Page 15: Functional programming ii

Recursion in Arts

Page 16: Functional programming ii

Recursion Use Cases

● Being Awesome● Traversing Data Structures

○ Lists○ Trees

● Formulas● Algorithms

● Often less error-prone than iteration!

Page 17: Functional programming ii

Recursion Example: Factorial

unsigned long factorial(unsigned long n) {

return

n == 0 ? 1

: n * factorial(n - 1);

}Base Case

Generic Case

Reduction

Page 18: Functional programming ii

Recursion Example: Echo

Case 1: Empty Listecho([]) :- /* Base Case */

nl.

Case 2: Single Wordecho([N]) :- /* Special 1-Element Case */

write(N), echo([]).

Case 3: Multiple Wordsecho([H|T]) :- /* Generic Case */

write(H), write(' '), echo([T]).

Page 19: Functional programming ii

Recursion Example: Echo

Case 1: Empty Listecho([]) :- /* Base Case */

nl.

Case 2: Single Wordecho([N]) :- /* Special 1-Element Case */

write(N), echo([]).

Case 3: Multiple Wordsecho([H|T]) :- /* Generic Case */

write(H), write(' '), echo([T]).

Page 20: Functional programming ii

Recursion Example: Lsimport java.io.*;

public class Ls { public static void main(final String... args) { if (args.length == 0) ls(new File(".")); for (final String pathname : args)

ls (new File(pathname)); } public static void ls(final File path) { System.out.println(path); if (path.isDirectory()) for (final File child : path.listFiles()) ls(child); }}

Page 21: Functional programming ii

Recursive Max Function in Haskell

maximum' :: (Ord a) => [a] -> a

maximum' [] = error "maximum of empty list"

maximum' [x] = x

maximum' (x:xs)

| x > maxTail = x

| otherwise = maxTail

where maxTail = maximum' xs

Page 22: Functional programming ii

Recursive Max Function in Haskell

maximum' :: (Ord a) => [a] -> a

maximum' [] = error "maximum of empty list"

maximum' [x] = x

maximum' (x:xs) = max x (maximum' xs)

Page 23: Functional programming ii

Quicksort in Haskell

qsort :: (Ord a) => [a] -> [a]

qsort [] = []

qsort (x:xs) =

qsort [a | a <- xs, a <= x]

++ [x] ++

qsort [a | a <- xs, a > x]

Disclaimer: This is not a true Quicksort (it’s not in-place), and not the fastest possible.

But behold its beauty!

Page 24: Functional programming ii

How to DevelopLinear Recursive Algorithms

1. Define Base caseaka Case 0 or Case 1

2. Define special cases, if any3. Define generic case

aka Case n → n-1 or Case n+1 → n

Page 25: Functional programming ii

Recursion in C

unsigned long factorial(unsigned long n) {

return

n == 0 ? 1

: n * factorial(n - 1);

}

Page 26: Functional programming ii

Disassembly (gcc -O1)factorial:.LFB0:

.cfi_startprocmovl $1, %eaxtestq %rdi, %rdije .L6pushq %rbx.cfi_def_cfa_offset 16.cfi_offset 3, -16movq %rdi, %rbxleaq -1(%rdi), %rdicall factorialimulq %rbx, %raxpopq %rbx.cfi_restore 3.cfi_def_cfa_offset 8

.L6:rep ret.cfi_endproc

Page 27: Functional programming ii

Disassembly (clang -O1)factorial: # @factorial

.cfi_startproc# BB#0:

movl $1, %eaxtestq %rdi, %rdije .LBB0_2.align 16, 0x90

.LBB0_1: # %tailrecurse # =>This Inner Loop Header: Depth=1

imulq %rdi, %raxdecq %rdijne .LBB0_1

.LBB0_2: # %tailrecurse._crit_edgeretq.cfi_endproc

Page 28: Functional programming ii

Disassembly (gcc -O2)factorial:.LFB0:

.cfi_startproctestq %rdi, %rdimovl $1, %eaxje .L4.p2align 4,,10.p2align 3

.L3:imulq %rdi, %raxsubq $1, %rdijne .L3rep ret

.L4:rep ret.cfi_endproc

Page 29: Functional programming ii

Conclusion

There are cases in which modern compilers● convert recursion into tail recursion● compile a tail recursion into a loop

Note: javac is stupid!Oracle, are you listening?!

Page 30: Functional programming ii

Fibonacci

unsigned long fibo(unsigned long n){ return

n < 2 ? 1 : fibo(n - 1) + fibo(n - 2);}

⇒ Too tough for most compilers!

Page 31: Functional programming ii

ConvertingRecursion → Tail Recursion

typedef unsigned long u64;

u64 fiboT(u64 a, u64 b, u64 n){ return n < 2 ? a : fiboT(b, a + b, n - 1);}

u64 fibo(u64 n){ return fiboT(1, 2, n);}

Page 32: Functional programming ii

Why are Functional Languagesbetter at Recursion?

● Pure functions● Memoization● (Why do impure functions not allow

memoization?)● Lazy Evaluation

Page 33: Functional programming ii

Algebraic Data Types

Page 34: Functional programming ii

Algebraic data type

Composite Type

Defined by one more more data constructors.

data List a = Nil | Cons x (List xs)

● ADT algebra○ Sum of data constructors - Disjoint unions○ Each constructor is Product of its arguments

Page 35: Functional programming ii

ADT Example

List definition in Scala

sealed trait List[+A]

case object Nil extends List[Nothing]

case class Cons[+A](head: A, tail: List[A])

extends List[A]

In OO terms these constructors can be mapped to subtypes.

Page 36: Functional programming ii

ADT Example

ADTs have finite number of constructors or subtypes.

For list we have two constructors or subtypesEmpty list Non empty list with head and tail.

These constructions are exhaustive and no new subtype will ever be added.

Page 37: Functional programming ii

More ADT Examples

OptionSome | None

EitherLeft | Right

TrySuccess | Failure

FutureSuccess | Failure

Page 38: Functional programming ii

Switch cases good or bad

OO programmers consider Switch cases bad. Conditional code. Hard to maintainHunt down existing cases to add new cases. Program will misbehave if you miss to update one.

FP enhanced switch cases to pattern matching and promote them.

What’s the Truth??

Page 39: Functional programming ii

Types and Operations

● Type against OperationsTy

pe

Operations

Infinite Type, Finite Operations

Finite Type, Infinite Operations

Page 40: Functional programming ii

Infinite Type, Finite OperationsImplemented as Polymorphism (class hierarchy).

Easy to add new Type by Subtyping.

Difficult to add new Operations.

Finite Type, Infinite OperationsRepresented by Algebraic data types (ADTs)

Easy to add new operations using Pattern matching.

Difficult to add new Types.

Page 41: Functional programming ii

Pattern Matching

Page 42: Functional programming ii

Check for given pattern in the input.

Constant Pattern

def describe(x: Any) = x match {

case 5 => "five"

case "hello" => "hi!"

case () => "Unit" // => everything is a value

}

Switch cases.. Enhanced

Page 43: Functional programming ii

Pattern Types

● Constructor Pattern

sealed class Expr

case class Number(num: Double) extends Expr

case class UnOp(operator: String, arg: Expr)

extends Expr

expr match {

case UnOp("-", UnOp("-", Number(a))) =>

println("Double Negation for" + a)

case _ => // return type unit ().

}

Page 44: Functional programming ii

Sequence Pattern

expr match {

case List(0, _, _) =>

println("3 element, starting with 0")

case List(0, _*) => println("starting with 0")

case List(_, _, *) => println("at least two")

case e => println("something else" + e)

// variable pattern

}

Variable pattern is catch all pattern.

Page 45: Functional programming ii

Type Pattern

def generalSize(x: Any) = x match {

case s: String => s.length

// typecast to string

case m: Map[_, _] => m.size

case _ => -1

}

Page 46: Functional programming ii

Type pattern

def isIntIntMap(x: Any) = x match {

case m: Map[Int, Int] => true

case _ => false

}

<console>:11: warning: non-variable type argument

Int in type pattern Map[Int,Int] is unchecked since

it is eliminated by erasure

case m: Map[Int, Int] => true

^

Page 47: Functional programming ii

Type erasers

Generic types in java and scala are only available at compile time.

Compiler use this information for type checking and then erase it.

Java and Scala arrays are exception to this rule.

def isStringArray(x: Any) = x match {

case a: Array[String] => "yes" // this works

case _ => "no"

}

Page 48: Functional programming ii

Pattern matching with ADTs

def printIfExists(a: Option[Int]) = {

a match {

case Some(x) =>

println("value provided " + x)

}

}

<console>:10: warning: match may not be exhaustive.It would fail on the following input: None

This check is possible because of ADTs

Page 49: Functional programming ii

Pattern match as Higher order function

def doubleTheOdds(numbers: List[Int]) = {

numbers.map {

case x if x%2 != 0 => x * 2

case y => y

}

}

The match is happening on the input of the map function.

If condition in pattern matching is called as Guard Condition.

Page 50: Functional programming ii

Partial functions with Pattern Matching

def printIfExists:

PartialFunction[Option[Int], Unit] = {

case Some(x) =>

println("value provided " + x)

}

This will compile without any warnings.

Also no match required (similar to HOF pattern matching)

Page 51: Functional programming ii

Pattern matching in variable declaration

Tuple values

val fullName = ("Jack", "Sparrow")

val (firstName, lastName) = fullName

Case class objects

val exp = BinOp("*", Number(5), Number(1))

val BinOp(op, left, right) = exp

Page 52: Functional programming ii

Pattern matching as value

val number = 10

val numberDescription = number match {

case 5 => "five"

case 10 => "Ten"

case _ => "something else"

}

Everything is value in function programming.

Also said as everything is an expression (not statement).

Page 53: Functional programming ii

Pattern Matching final Example

val numbers = List(1, 2, 3, 4, 5, 6)numbers.collect { case x if x%2 == 0 => x * 2 }output => List(4, 8, 12)

This is equivalent to

val partial: PartialFunction[Int, Int] = { case x if x % 2 == 0 => x * 2

}

numbers.filter(partial.isDefinedAt).map(partial)

Page 54: Functional programming ii

I’ve seen the future.

● It’s logical.

● It has pattern matching……on the right side, which is left!

Pattern matching beyond FP?

Page 55: Functional programming ii

main(Argv) :- echo(Argv).

echo([]) :-

nl.

echo([Last]) :-

write(Last), echo([]).

echo([H|T]) :-

write(H), write(' '), echo(T).

Echo in Prolog

Page 56: Functional programming ii

Expression Problem

Page 57: Functional programming ii

Problem

What can be done when neither Types nor Operations are limited.

Is there a way to Add a new type easilyAdd a new operation easily

This problem is well known as Expression Problem.

Page 58: Functional programming ii

Philip Wadler 1998

American Computer Scientist

He coined the term Expression Problem

Expression Problem

Page 59: Functional programming ii

Definition

Expression Problem:The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g., no casts)

Used in discussing strengths and weaknesses (expressive power) of various programming paradigms and programming languages.

The problem manifest itself while working with language expressions.

Page 60: Functional programming ii

interface Exp { void print(); }

class Lit implements Exp {

protected int value;

Lit(int value) { this.value = value; }

public void print() { System.out.print(value); }

}

class Add implements Exp {

protected Exp left, right;

Add(Exp l, Exp r) { left = l; right = r; }

public void print() {

return left.print(); Syso.print(“+”); right.print();

}

}

Page 61: Functional programming ii

interface EvalExp extends Exp {

int eval();

}

class EvalAdd extends Add implements EvalExp {

public int eval() {

return left.eval() + right.eval();

}

}

// Ouch! This will not compile.

Page 62: Functional programming ii

Generics to the Rescue

We can solve the compilation problem by somehow limiting the left and right to EvalExp

We can do that by introducing a type parameter for EvalAdd class.

class EvalAdd<A extends EvalExp> extends Add implements EvalExp

But this does not change the type of the left or right.

We will have to parameterize Add too.

Page 63: Functional programming ii

Simple Solution with Generics

class Add<A extends Exp> implements Exp { protected A left, right;

Add(A l, A r) { left = l; right = r; } public void print() { … }

}

class EvalAdd<A extends EvalExp> extends Add<A> implements EvalExp {

public int eval() {

return left.eval() + right.eval();

// Now this will work.

}

}

Page 64: Functional programming ii

Analysing the solution

The solution is possible because of Generics which came from FP languages into Java.

But we do not like this solution.

The base interface Exp does not enforce type parameter on derived classes.

This version was simplified version of actual proposed solution.

Page 65: Functional programming ii

Type classes

Expression problem can be solved by using type classes

Haskell was the first language to introduce type classes.

They are supported by Scala with the help of implicits.

Page 66: Functional programming ii

Example

sealed trait Exp

case class Lit(value: Int) extends Exp

case class Add(left: Exp, right: Exp) extends Exp

object ExpEvaluator {

def eval(exp : Exp): Int = exp match {

case Lit(value) => value

case Add(left, right) =>

eval(left) + eval(right)

}

}

Page 67: Functional programming ii

We would like to convert Expression to Json.

object JsonWriter {

def write(value: JsonValue): String = // ...

def write(value: Exp) = // ...

}

But, we should be able to use JsonWriter for any object not just Expressions.

Page 68: Functional programming ii

How about this

trait JsonConvertible {

def toJson: JsValue

}

sealed trait Exp extends JsonConvertible

object JsonWriter {

def write(value: JsonValue): String = // ...

def write(value: JsonConvertible) =

write(value.toJson)

}

Page 69: Functional programming ii

This works.

JsonWriter.write(Add(Lit(2), Lit(3)))

But wait! .. Why the Expression class needs to know anything about Json.

We would like to remove the Expression dependency on JsonCovertible.

This implementation is also called as Subtype Polymorphism

Page 70: Functional programming ii

We can solve the coupling problem by introducing a Type class as follows

// type class

trait JsonConverter[A] {

def convertToJson(value: A): JsonValue

}

object JsonWriter {

def write(value: JsonValue): String = // ...

def write[A](value: A, conv: JsonConverter[A]) =

write(conv.convertToJson(value))

}

Page 71: Functional programming ii

We need to implement a convertor.

def expJsonConverter = new JsonConverter[Exp] {

def convertToJson(exp: Exp): JsonValue = { ... }

}

JsonWriter.write(Add(Lit(2), Lit(3)),

expJsonConverter)

This removed the coupling between Exp and Json.

But we have to pass in a parameter, which can quickly become complex for Composite type.

Page 72: Functional programming ii

Scala implicits can help here.

implicit def expJsonConverter = // ...

def write[A](value: A)(implicit conv: JsonConverter[A]) =

write(conv.convertToJson(value))

JsonWriter.write(Add(Lit(2), Lit(3)))

Implicits parameters are passed in by the Compiler.

The call is now exactly like when JsonCovertible was used.

Page 73: Functional programming ii

More about Type classes

Type classes provide something called as Ad-hoc polymorphism.

Type classes allow you to add new functionality to the existing Types, without changing them.Note, they are different than extension methods.

Like extension method, they do not attach themself to the type they are working on.

Page 74: Functional programming ii

More about Type classes

Type classes provide polymorphism on their type parameters.

Interfaces in OO, provide polymorphism over the calling object.

Type classes can accept more than one type parameters and hence can provide polymorphism on two different type parameters. This is not possible for interfaces.

Page 75: Functional programming ii

More about Type classes

Compiler picks-up the right type classes on the type parameter.

So unlike interfaces, implementation is chosen at compile time

Type classes depend on their type parameters and not with the object so they are more like Generic Static Method.

Page 76: Functional programming ii

Solving Expression Problem with Type classes

trait Exp

case class Lit(value: Int) extends Exp

case class Add[A <: Exp, B <: Exp](left: A, right:

B) extends Exp

[A <: Exp] is same as <A extends Exp> in Java

Page 77: Functional programming ii

Define type class and implicit implementations

// type classtrait Eval[E] {

def eval(e: E): Int

}

implicit def litEval = new Eval[Lit] {

def eval(l: Lit) = l.value

}

Page 78: Functional programming ii

Implementation for Add is little complex.

implicit def addEval[A <: Exp, B <: Exp]

(implicit e1: Eval[A], e2: Eval[B]) = {

new Eval[Add[A, B]] {

def eval(a: Add[A, B]) =

e1.eval(a.left) + e2.eval(a.right)

}

}

The method takes two implicit arguments to evaluate left and right exp.

In, Add[A, B], A and B are type of Exp which can be Add or Lit

Page 79: Functional programming ii

Evaluating the Expressions

def expressionEvaluator[A <: Exp]

(exp: A)(implicit e : Eval[A]) = {

e.eval(exp)

}

Lets use it to evaluate something// (1 + 2) + 3val exp1 = Add(Add(Lit(1), Lit(2)), Lit(3))

println("" + expressionEvaluator(exp1))

// output => 6

Page 80: Functional programming ii

Let add a new type

case class Mult[A <: Exp, B <: Exp]

(left: A, right: B) extends Exp

Define an Implicit evaluator for Mult (same as Add)

implicit def mulEval[A <: Exp, B <: Exp]

(implicit e1: Eval[A], e2: Eval[B]) = {

new Eval[Mult[A, B]] {

def eval(m : Mult[A, B]) =

e1.eval(m.left) * e2.eval(m.right)

}

}

Page 81: Functional programming ii

This will allow us to evaluate Expression with Mult

// (3 + 4) * 7val exp2 = Mult(Add(Lit(3), Lit(4)), Lit(7))

println("" + expressionEvaluator(exp2))

// output => 49

Lets try to add new operation like Print.

Page 82: Functional programming ii

Type class for Print operation

// type classtrait Print[P] {

def print(p: P): Unit

}

Implicit implementation for Lit type

implicit def litPrint = new Print[Lit] {

def print(l: Lit) = Console.print(l.value)

}

Page 83: Functional programming ii

Similarly implicit implementation for Add and Mult

implicit def addPrint[A <: Exp, B <: Exp]

(implicit p1: Print[A], p2 : Print[B]) = {

new Print[Add[A, B]] {

def print(a : Add[A, B]) = {

p1.print(a.left);

Console.print(" + ");

p2.print(a.right);

}

}

Implementation for Mult is exactly same.

Page 84: Functional programming ii

Define a method to print the expression

def printExpressions[A <: Exp]

(exp : A)(implicit p : Print[A]) = {

p.print(exp)

}

Use it to print the expressions.// (3 + 4) * 7val exp2 = Mult(Add(Lit(3), Lit(4)), Lit(7))

printExpressions(exp2)

// output => 3 + 4 * 7

Page 85: Functional programming ii

TestingFunctional Programming

Page 86: Functional programming ii

● In General: Easier than OO (without FP)○ Pure Functions are almost trivial to test.○ No Side-effects ⇒ No additional states○ Higher Order Functions can be mocked easily.

● But what about TDD? How do you approach TDD for Functional Programming?

Testing Functional Programming

Page 87: Functional programming ii

Transformation Priority Premise

● What is a Transformation?

● Let’s talk about Refactoring.

“Uncle Bob”, My Hero Robert C. Martin →

Page 88: Functional programming ii

Refactoring

“Refactoring is changing the structure of code without significantly changing its behavior.”

‒ Robert C. Martin

Page 89: Functional programming ii

Transformation

“Transformation is changing the behavior of code without significantly changing its structure.”

‒ Robert C. Martin

Page 90: Functional programming ii

Transformation Priority Premise({} → nil) no code at all -> code that employs nil(nil->constant)

(constant → constant+) a simple constant to a more complex constant(constant → scalar) replacing a constant with a variable or an argument(statement → statements) adding more unconditional statements.(unconditional → if) splitting the execution path(scalar → array)

(array → container)

(statement → recursion)

(if → while)

(expression → function)replacing an expression with a function or algorithm

(variable → assignment) replacing the value of a variable

Page 91: Functional programming ii

Beyond FP

● FP and OO are NOT mutually exclusive● FOOP - Functional Object Oriented

Programming● Logic Programming

Page 92: Functional programming ii

Thank you!Questions?

Page 93: Functional programming ii

Backup Slides

Page 94: Functional programming ii

TODO Testing

● Testing Pure First-Order Functions● Testing Pure Higher-Order Functions

○ Mocking in FP● Transformation Priority Premise for FP

○ Example how to develop from Base Case to N-Case

Page 95: Functional programming ii

List Implementation - optional

package mylist

sealed trait List[A] {

def ::(e: A): List[A] = mylist.::(e, this)

def :::(l: List[A]): List[A] = ???

}

case object Nil extends List[Nothing]

case class ::[A](h: A, tail: List[A]) extends List[A]

Page 96: Functional programming ii

Topic List[P] Side Effects Continued[C] Recursion[P] Algebraic Datatypes[P] Pattern Matching[P] Expression Problem[C] Streams in Java (creation, consumption, filtering, map, reduce etc.)[P] Streams in Scala (creation, consumption, filtering, map, reduce etc.)[C] Testing in Functional Programming (TDD for FP, BDD, TPP for FP)MonadsMonoidsFunctorsApplicativesF-Bounded Types

Page 97: Functional programming ii

Recursion TODO● Replacing Assignments with Calls● Replacing Loops with Recursion● Developing Recursive Algorithms

○ Base Case / Most Degenerate Case○ N-Case○ List-Variants: Empty, 1 Element, Multiple○ Head-Tail-Recursion vs Partition Recursion

● Tail-Recursion○ Why○ How to convert normal recursion into tail recursion (example: factorial) by introducing a

result parameter○ Examples in C with Disassemblies by GCC, clang, armcc, c251○ Example in Prolog○ Example in Scala○ Example in Clojure○ Limitations in Java