purely functional data structures

113
Purely functional data structures @tkaczmarzyk blog.kaczmarzyk.net Tomek “Kosior” Kaczmarzyk

Upload: tomasz-kaczmarzyk

Post on 13-Apr-2017

442 views

Category:

Software


4 download

TRANSCRIPT

Purely functionaldata structures

@tkaczmarzykblog.kaczmarzyk.net

Tomek “Kosior” Kaczmarzyk

Purely functionaldata structures

@tkaczmarzykblog.kaczmarzyk.net

Tomek “Kosior” Kaczmarzyk

meh...

?WAT!?

Achievement unlocked!WTF Level 3

whoami

Just wandering around the world, developing software

@tkaczmarzykblog.kaczmarzyk.net

Tomek “Kosior” Kaczmarzyk

Recently hired by Siili Solutions

Agenda

1. Examples of PF data structures

2. GIT internals

- lists- queues

“Bad programmers worry about the code.Good programmers worry aboutdata structures and their relationships.”

Linus Torvalds

Part 1:PF data structures

- how do they work?

Java

public class DefensiveList<T> {

private List<T> values; public DefensiveList(List<T> values) { values = new ArrayList<>(values); }

public DefensiveList<T> add(T elem) { List<T> copy = new ArrayList<>(values); copy.add(elem); return new DefensiveList<T>(copy); }}

functional lists

[1, 2, 3].head() = 1

[1, 2, 3].tail() = [2, 3]

[1, 2, 3].cons(7) = [7, 1, 2, 3]

1

2

1

3

2

1

3

2

1

Nil

3 Nil

2

1

mylist =

3 Nil

2

1

mylist =

= mylist.tail()

3 Nil

2

1

7mylist =

= mylist.tail()

= mylist.tail().cons(7)

functional queues

[1, 2, 3].head() = 1

[1, 2, 3].tail() = [2, 3]

[1, 2, 3].cons(7) = [7, 1, 2, 3]

[1, 2, 3].snoc(7) = [1, 2, 3, 7]

q = { [1, 2, 3], [6, 5, 4] }

front list

rear list

(reversed)

{ [], [] }

snoc(2)

{ [2], [] }

{ [], [] }

snoc(2)

{ [2], [] }

{ [], [] }

snoc(3)

{ [2], [3] }

snoc(2)

{ [2], [] }

{ [], [] }

snoc(3)

{ [2], [3] }snoc(4)

{ [2], [4, 3] }

snoc(2)

{ [2], [] }

{ [], [] }

snoc(3)

{ [2], [3] }snoc(4)

{ [2], [4, 3] }snoc(5)

{ [2], [5, 4, 3] }

tail

{ [2], [5, 4, 3] }

{ [], [5, 4, 3] } ???

tail

{ [2], [5, 4, 3] }

{ [], [5, 4, 3] } ???

{ [3, 4, 5], [] }

Computionalcost

$$$

{ [1, 2], [] }

snoc(3)

{ [1, 2], [3] }

snoc

snoc

(3)

(4)

{ [1, 2], [4, 3] }

snoc

snoc

(3)

(4)tail

{ [2], [4, 3] }

snoc

snocsnoc

(3)

(4) (5)tail

{ [2], [5, 4, 3] }

snoc

snoc tailsnoc

(3)

(4) (5)tail

{ [3, 4, 5], [] }

snoc

snoc tailsnoc

(3)

tail(4) (5)

tail

{ [4, 5], [] }

snoc

snoc tailsnoc

(3)

tail(4) (5)

tail

O(1)

O(1) O(1) O(1) O(m)

{ [4, 5], [] }

O(1)

snoc

snoc tailsnoc

(3)

tail(4) (5)

tail

O(1)

O(1) O(1) O(1) O(m)

{ [4, 5], [] }

O(1)

newList = mylist.snoc(3) .snoc(4) .tail() .snoc(5) .tail() .tail();

newList = mylist.snoc(3) .snoc(4) .tail() .snoc(5);

newList.tail();newList.tail();newList.tail();newList.tail();

The problemof multiple futures

Achievement unlocked!Tribute

snoc

snoc tailsnoc

(3)

tail(4) (5)

tail

snoc

snoc tailsnoc

(3)

tail(4) (5)

tail

tail

tail

tail

tail tail

tail

snoc(6)

lazy evaluation& memoizationto the rescue!

class Suspension<T> { private Supplier<T> suspendedFun; private T calculatedValue; public Suspension(Supplier<T> suspendedFun) { this.suspendedFun = suspendedFun; } public T force() { if (calculatedValue == null) { calculatedValue = suspendedFun.get(); } return calculatedValue; }}

Suspension<Integer> memoizedRandom = new Suspension<>(() -> new Random().nextInt()); println(memoizedRandom.force()); // genereted here println(memoizedRandom.force()); // cached valueprintln(memoizedRandom.force()); // on subsequent calls

interface Stream<T> { Stream<T> tail(); Stream<T> append(Suspension<Stream<T>> other); Stream<T> reverse(); boolean isEmpty();}

interface Stream<T> { Stream<T> tail(); Stream<T> append(Suspension<Stream<T>> other); Stream<T> reverse(); boolean isEmpty();}

class Empty<T> implements Stream<T> {

public Stream<T> tail() { throw new UnsupportedOptionException(); }

public Stream<T> append(Suspension<Stream<T>> other){ return other.force(); }

public Stream<T> reverse() { return this; }

public boolean isEmpty() { return true; }}

class NonEmpty<T> implements Stream<T> { private T head; private Suspension<Stream<T>> tail;

public NonEmpty(T head, Suspension<Stream<T>> tail) { this.head = head; this.tail = tail; } public Stream<T> tail() { return tail.force(); }

public Stream<T> append(Suspension<Stream<T>> other) { return new NonEmpty<>( this.head, new Suspension<>(() -> this.tail().append(other))); }

public Stream<T> reverse() { // executes the full reverse... }

public boolean isEmpty() { return false; }}

class Queue<T> { private Stream<T> front; private int lenF; private Stream<T> rear; private int lenR; private Queue(Stream front, int lenF, Stream rear, int lenR){ // ordinary assignments... } public static <T> Queue<T> queue( Stream front, int lenF, Stream rear, int lenR){

if (lenR <= lenF) { return new Queue<T>(front, lenF, rear, lenR); } else return new Queue<T>( front.append(new Suspension<>(() -> rear.reverse())), lenF + lenR, new Empty<T>(), 0); }

public Queue<T> tail() { return queue(front.tail(), lenF - 1, rear, lenR); } }

Q { [1, 2, 3, 4], [7, 6, 5] }

Q { [2, 3, 4], [7, 6, 5] }

tail

Q {[3, 4].append(susp([7, 6, 5].rev())), []}

tail

tail

suspended reverse

is created here

Q {[4].append(susp([7, 6, 5].rev())), []}

tail

tail

suspended reverse

is created here

tail

Q {[5, 6, 7], []}

tail

tail

suspended reverse

is created here

tail tail

and forced here!

O(m)

Q {[5, 6, 7], []}

tail

tail

suspended reverse

is created here

tail tail

and forced here!

O(m)

at least

m x tail()

with O(1)

tail

tail tail tailat least

m x tail()

with O(1)

tail

tail tail tailat least

m x tail()

with O(1)

tail tail

tail tail

tail

tail tail tailat least

m x tail()

with O(1)

tail tail

tail tail

it's the same (==)

Suspension!

tail

tail tail tailat least

m x tail()

with O(1)

tail tail

tail tail

it's the same (==)

Suspension!

tail

tail

tail

tail tail tailat least

m x tail()

with O(1)

tail tail

tail tail

it's the same (==)

Suspension!

tail

tail

different Suspensionsbut each pays for m x O(1)

amortizationvs

worst-case

1 opnumber

0

1

20

systemresponse time

[s]

2 3 ... 100 200... 199

worst-case DSamortized DS

there is a solutionfor that as well

but let's skip the details :)

Part 2:Git internals

becoming legendary

workingdirectory

repository

workingdirectory

repository

file A

file B

workingdirectory

repository

file A

file B

A1 B1

workingdirectory

repository

file A

file B

A1 B1snapshot1

workingdirectory

repository

file A

file B

A1 B1snapshot1

(v2)

workingdirectory

repository

file A

file B

A1 B1snapshot1

B2snapshot2(v

2)

workingdirectory

repository

file A

file B

A1 B1snapshot1

B2snapshot2

(v2)

(v2)

workingdirectory

repository

file A

file B

A1 B1snapshot1

B2snapshot2

dir1

file C

(v2)

(v2)

workingdirectory

repository

file A

file B

A1 B1snapshot1

snapshot2

B2

dir1

file C

snapshot3

A2

C1

(v2)

(v2)

ec11312 386ad51 6740724

1796e2f

cca7cf6

ec11312 386ad51 6740724

1796e2f

cca7cf6

fileA.txt fileB.txt dir1

fileC.txt

100644 blob fa49b07797 fileA.txt100644 blob 1f7a7a472a fileB.txt040000 tree d8329fc1cc7 dir1

just a file!

repository

.git/objects

00

1c

ff

......

1674f7...

96e261...

ec1131...

commit1

commit2

commit3

commit tree da3b911d93889f6f35ddf4parent e78dca6f8f5e7dc825ded27094e0author Kosior <[email protected]>committer Kosior <[email protected]>

example commit :)

once again:just a text file!

88bd

2a3ac

9a789

8ffda

88bd

2a3ac

9a789

8ffda master

hotfix

88bd

2a3ac

9a789

8ffda

hotfix

HEAD

master

HEAD

master

HEAD

master

HEAD

master

HEAD

master

Is Git a purely functionaldata structure!?

88bd

2a3ac

9a789

8ffda

hotfix

master

a36c

HEAD

88bd

2a3ac

9a789

8ffda

hotfix

master

a36c HEAD

88bd

2a3ac

9a789

8ffda

hotfix

master

a36c HEAD7e89

88bd

2a3ac

9a789

8ffda

hotfix

master

a36c

HEAD

7e89

6f3a2

88bd

2a3ac

9a789

8ffda

hotfix

master

a36c

HEAD

7e89

6f3a2

88bd

2a3ac

9a789

8ffda

hotfix

master

a36c

HEAD

7e89

6f3a2still avail

able

(until gc)

the power of simplicity

recap

* immutability is powerful

* PF data structures can be damn effective!

* they can become a way to create legendary software :-)

Purely functionaldata structures

@tkaczmarzykblog.kaczmarzyk.net

thanks!

Tomek “Kosior” Kaczmarzyk

Acknowledgements:“Purely functional data structures” by Chris Okasakihttp://git-scm.com/book

Photo credits:https://www.flickr.com/photos/jdhancock/9544541664/https://www.flickr.com/photos/janimut/14609601810/https://www.flickr.com/photos/macwagen/2819684/https://www.flickr.com/photos/matt_gibson/2580896289/https://www.flickr.com/photos/hktang/4243300265/https://www.flickr.com/photos/funfilledgeorgie/10922459733/https://www.flickr.com/photos/celinesphotographer/ 4044569502/https://www.flickr.com/photos/edsonhong1/5685375541/https://www.flickr.com/photos/ch-weidinger/17341905336/https://www.flickr.com/photos/toomuchdew/9284836919/