if you think you can stay away from functional programming, you are wrong

Post on 15-Jan-2015

2.677 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

TRANSCRIPT

by Mario Fuscomario.fusco@gmail.comtwitter: @mariofusco

Moore's

law

The number of transistors on integrated

circuits doubles approximately every two years

Moore's

law

The number of transistors on integrated

circuits doubles approximately every two years

Now achieved

by increasing

the number

of cores

Moore's

law

The number of transistors on integrated

circuits doubles approximately every two years

Now achieved

by increasing

the number

of cores

Fortran

C / C++Java

Lisp

MLHaskell

Add abstractions

C#

Algol

Subtract abstractions

Imperative languages

Functional languages

Scala

F#

Hybrid languages

Amdahl's

law

The speedup of a program using multiple processors

in parallel computing is limited by the time needed

for the sequential fraction of the program

Concurrency & Parallelism

Parallel programmingRunning multiple tasks at

the same time

Concurrent programmingManaging concurrent requests

Both are hard!

The cause of the problem …

Mutable state +

Parallel processing =

Non-determinism

The cause of the problem …

Mutable state +

Parallel processing =

Non-determinism

Functional

Programming

Race conditions

Deadlocks

Starvation

Livelocks

… and its effects

Too hard to think about them!

Race conditions

Deadlocks

Starvation

Livelocks

… and its effects

The native Java concurrency model

Based on:

They are sometimes plain evil …

… and sometimes a necessary pain …

… but always the wrong default

Threads

Semaphores

SynchronizationLocks

Different concurrency models

Isolated mutable state (actors)

Purely immutable (pure functions)

Shared mutable state

(threads + locks)

Summing attendants ages (Threads)

class Blackboard {

int sum = 0;

int read() { return sum; }

void write(int value) { sum = value; }

}

class Attendant implements Runnable {

int age;

Blackboard blackboard;

public void run() {

synchronized(blackboard) {

int oldSum = blackboard.read();

int newSum = oldSum + age;

blackboard.write(newSum);

}

}

}

Summing attendants ages (Actors)

class Blackboard extends UntypedActors {

int sum = 0;

public void onReceive(Object message) {

if (message instanceof Integer) {

sum += (Integer)message;

}

}

} class Attendant {

int age;

Blackboard blackboard;

public void sendAge() {

blackboard.sendOneWay(age);

}

}

Summing attendants ages (Functional)

class Blackboard {

final int sum;

Blackboard(int sum) { this.sum = sum; }

}

class Attendant {

int age;

Attendant next;

public Blackboard addMyAge(Blackboard blackboard) {

final Blackboard b = new Blackboard(blackboard.sum + age);

return next == null ? b : next.myAge(b);

}

}

The state quadrantsMutable

Immutable

Shared

Unshared

Actors

Functional

Programming

Threads

Concurrency friendly

Concurrency unfriendly

OOP makes code understandable

by encapsulating moving parts

FP makes code understandable

by minimizing moving parts

- Michael Feathers

OOP vs FP

The OOP/FP dualism - OOPpublic class Bird { }

public class Cat {

private Bird catch;

private boolean full;

public void capture(Bird bird) {

catch = bird;

}

public void eat() {

full = true;

catch = null;

}

}

Cat cat = new Cat();

Bird bird = new Bird();

cat.capture(bird);

cat.eat();

The story

The OOP/FP dualism - FPpublic class Bird { }

public class Cat {

public CatWithCatch capture(Bird bird) { return new CatWithCatch(bird); }

}

public class CatWithCatch {

private final Bird catch;

public CatWithCatch(Bird bird) { catch = bird; }

public FullCat eat() { return new FullCat(); }

}

public class FullCat { }

BiFunction<Cat, Bird, FullCat> story =

((BiFunction<Cat, Bird, CatWithCatch>)Cat::capture)

.compose(CatWithCatch::eat);

FullCat fullCat = story.apply( new Cat(), new Bird() );

The OOP/FP dualism - FPpublic class Bird { }

public class Cat {

public CatWithCatch capture(Bird bird) { return new CatWithCatch(bird); }

}

public class CatWithCatch {

private final Bird catch;

public CatWithCatch(Bird bird) { catch = bird; }

public FullCat eat() { return new FullCat(); }

}

public class FullCat { }

BiFunction<Cat, Bird, FullCat> story =

((BiFunction<Cat, Bird, CatWithCatch>)Cat::capture)

.compose(CatWithCatch::eat);

FullCat fullCat = story.apply( new Cat(), new Bird() );

Immutability

Emphasis on verbs

instead of names

No need to test internal state: correctness enforced by the compiler

for (Employee e : employees) {

e.setSalary(e.getSalary() * 1.03);

}

employees.forEach(e -> e.setSalary(e.getSalary() * 1.03));

Inherently serial

Client has to manage iteration

Nested loops are poorly readable

+ Library is in control � opportunity for internal optimizations as parallelization,

lazy evaluation, out-of-order execution

+ More what, less how � better readability

+ Fluent (pipelined) operations � better readability

+ Client can pass behaviors into the API as data �

possibility to abstract and generalize over behavior �

more powerful, expressive APIs

Not only a syntactic change!

Internal vs External Iteration

� Encourages a pipelined ( "fluent" ) usage style

� Operations are divided between intermediate and terminal

� Lazy in nature: only terminal operations actually trigger a computation

employees.stream()

.filter(e -> e.getIncome() > 50000)

.map(e -> e.getName())

.forEach(System.out::println);

Streams - Efficiency with laziness

� Encourages a pipelined ( "fluent" ) usage style

� Operations are divided between intermediate and terminal

� Lazy in nature: only terminal operations actually trigger a computation

employees.stream()

.filter(e -> e.getIncome() > 50000)

.map(e -> e.getName())

.forEach(System.out::println);

parallelStream()

… and parallelism for free

Streams - Efficiency with laziness

Is there such thing as a free lunch?

Probably yes …

… but we need functional

forks and knives to eat it

�Reassigning a variable

�Modifying a data structure in place

� Setting a field on an object

� Throwing an exception or halting with an error

�Printing to the console

�Reading user input

�Reading from or writing to a file

�Drawing on the screen

A program created using only pure functions

What is a functional program?

No side effects allowed like:

Functional programming is a restriction on how we

write programs, but not on what they can do

}}

What is a (pure) function?

A function with input type A and output type B

is a computation which relates every value a of

type A to exactly one value b of type B such

that b is determined solely by the value of a

But, if it really is a

function, it will do

nothing else

Referential transparency

An expression e is referentially transparent if for all programs p,

all occurrences of e in p can be replaced by the result of

evaluating e, without affecting the observable behavior of p

A function f is pure if the expression f(x) is referentially

transparent for all referentially transparent x

RTString x = "purple";

String r1 = x.replace('p', 't');

String r2 = x.replace('p', 't');

String r1 = "purple".replace('p', 't');

r1: "turtle"

String r2 = "purple".replace('p', 't');

r2: "turtle"

StringBuilder x = new StringBuilder("Hi");

StringBuilder y = x.append(", mom");

String r1 = y.toString();

String r2 = y.toString();

String r1 = x.append(", mom").toString();

r1: "Hi, mom"

String r2 = x.append(", mom").toString();

r1: "Hi, mom, mom"

Non-RT

vs.

RT wins

�Under a developer point of view:

� Easier to reason about since effects of evaluation are

purely local

�Use of the substitution model: it's possible to replace a

term with an equivalent one

�Under a performance point of view:

� The JVM is free to optimize the code by safely reordering

the instructions

�No need to synchronize access to shared data

�Possible to cache the result of time consuming functions

(memoization), e.g. with

Map.computeIfAbsent(K key,

Function<? super K,? extends V> mappingFunction)

Mutability

�Parameter binding is about assigning names to things

�Mutating variables is about assigning things to names

Does that second

one sound weird?

… well it's because

it IS weird

Immutability

� Immutable objects can be shared among

many threads exactly because none of

them can modify it

� In the same way immutable (persistent)

data structures can be shared without any

need to synchronize the different threads

accessing them

5

8

7 9

3

4

EEEEE E

E

Persistent Collections

5

8

7 9

3

4

EEEEE E

E

2

Persistent Collections

5

8

7 9

3

4

EEEEE E

E

5

3

2

EE

Persistent Collections

5

8

7 9

3

4

EEEEE E

E

5

3

2

EE

Shared data

Old Root

New Root

Persistent Collections

Modularityclass Player {

String name;

int score;

}

public void declareWinner(Player p) {

System.out.println(p.name + " wins!");

}

public void winner(Player p1, Player p2) {

if (p1.score > p2.score) declareWinner(p1)

else declareWinner(p2);

}

Modularityclass Player {

String name;

int score;

}

public void declareWinner(Player p) {

System.out.println(p.name + " wins!");

}

public void winner(Player p1, Player p2) {

if (p1.score > p2.score) declareWinner(p1)

else declareWinner(p2);

}

public Player maxScore(Player p1, Player p2) {

return p1.score > p2.score ? p1 : p2;

}

public void winner(Player p1, Player p2) {

declareWinner(maxScore(p1, p2));

}

Separate

computational logic

from side effects

Modularityclass Player {

String name;

int score;

}

public void declareWinner(Player p) {

System.out.println(p.name + " wins!");

}

public void winner(Player p1, Player p2) {

if (p1.score > p2.score) declareWinner(p1)

else declareWinner(p2);

}

public Player maxScore(Player p1, Player p2) {

return p1.score > p2.score ? p1 : p2;

}

public void winner(Player p1, Player p2) {

declareWinner(maxScore(p1, p2));

}

Separate

computational logic

from side effects

declareWinner(players.reduce(maxScore).get())

reuse maxScore to compute the winner among a list of players

A pure functional core

functional

core

Any function with side-effects can be split into a pure function at the

core and a pair of functions with side-effect. This transformation can

be repeated to push side-effects to the outer layers of the program.

Key Takeaways

� Strive for immutability

� Confine side-effects

� Avoid blocking code

� Cede control with higher-order functions

� Leverage referential transparency

� Use FP to design more composable and reusable API

… but there are no dogmas

� Be pragmatic and use the right tool for the job at hand

Poly-paradigm programming is more powerful

and effective than polyglot programming

Mario Fusco

Red Hat – Senior Software Engineer

mario.fusco@gmail.com

twitter: @mariofusco

Q A

Thanks … Questions?

top related