Slide 1

Slide 1 text

Functional Programming … in PHP (Warning: Contains traces of Math)

Slide 2

Slide 2 text

What is Functional Programming?

Slide 3

Slide 3 text

Functional Programming is programming with functions.

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

What is a function?

Slide 6

Slide 6 text

Marc Claire Adam Nora Coffee Tea A B

Slide 7

Slide 7 text

Marc Claire Adam Nora Coffee Tea A B

Slide 8

Slide 8 text

Marc Claire Adam Nora Coffee Tea A B

Slide 9

Slide 9 text

Marc Claire Adam Nora Coffee Tea f : A ! B A B Functions, like in mathematics

Slide 10

Slide 10 text

Functions in code

Slide 11

Slide 11 text

Rule #1 Always return the same output for the same input.

Slide 12

Slide 12 text

Rule #1 Always return the same output for the same input. Rule #2 Do nothing else aka. no side effects.

Slide 13

Slide 13 text

Turns out we do have a name for functions in programming.

Slide 14

Slide 14 text

Turns out we do have a name for functions in programming. We call them pure functions

Slide 15

Slide 15 text

// Int -> Int function increment(int $x): int { return $x + 1; } echo increment(2); // 3 echo increment(2); // 3 // String -> String function ask($s) { return $s.’?'; } echo ask('I am pure'); // I am pure? echo ask('I am pure'); // I am pure? Pure functions Our sets from earlier. Think of it as the function’s interface.

Slide 16

Slide 16 text

// Int -> Int function constant($x) { return 1; } echo constant(4); // 1 echo constant(4); // 1 // String -> Bool function startsWithP($s) { return $s[0] === 'P'; } echo startsWithP('Pure'); // True echo startsWithP('Pure'); // True echo startsWithP('Impure'); // False echo startsWithP('Impure'); // False Pure functions

Slide 17

Slide 17 text

Violating rule #1 // Int -> Int function f($x) { return random_int(0, $x); } echo f(10); // 4 echo f(10); // 7 // Int -> Int function g($x) { global $y; return $x + $y; } $y = 1; echo g(2); // 3 $y = 5; echo g(2); // 7

Slide 18

Slide 18 text

// String -> String function f($s) { echo 'I am not pure anymore :('; return $s; } // String -> String function g($s) { file_put_contents( '/tmp/some.log', 'I am not pure anymore :(’ ); return $s; } Violating rule #2

Slide 19

Slide 19 text

What can we do if we only have functions?

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

f ( x ) = x + 1

Slide 22

Slide 22 text

We are used to seeing functions written like this. f ( x ) = x + 1

Slide 23

Slide 23 text

We are used to seeing functions written like this. f ( x ) = x + 1 f : x ! x + 1 But we don’t need x to be able to talk about f.

Slide 24

Slide 24 text

f ( x ) = x + 1 x ! x + 1 We are used to seeing functions written like this. In fact we don’t need to label it f or anything at all.

Slide 25

Slide 25 text

f ( x ) = x + 1 x ! x + 1 This is called a lambda expression.

Slide 26

Slide 26 text

function f() { return function ($x) { return $x + 1; }; } Lambdas

Slide 27

Slide 27 text

function f() { return function ($x) { return $x + 1; }; } Lambdas At this point it has no label referring to it, it is just a value. f, when called, returns our \x —> x + 1 from the previous slide

Slide 28

Slide 28 text

function f() { return function ($x) { return $x + 1; }; } $aLabel = f(); echo $aLabel(1); // 2 Lambdas Calling f returns our lambda, which will now be bound to a label (\x —> x + 1)(1) = 1 + 1 = 2 At this point it has no label referring to it, it is just a value. f, when called, returns our \x —> x + 1 from the previous slide

Slide 29

Slide 29 text

Lambdas function f($g, $x) { return $g($x); } echo f(function ($x) { return $x + 1; }, 1); // 2 Hi there, I'm a lambda being passed around…

Slide 30

Slide 30 text

You have just witnessed Higher-order functions

Slide 31

Slide 31 text

You said only functions, but there was + and 1 …

Slide 32

Slide 32 text

Alonzo Church

Slide 33

Slide 33 text

Lambda calculus $c0 = function ($s, $z) { return $z; }; $c1 = function ($s, $z) { return $s($z); }; $c2 = function ($s, $z) { return $s($s($z)); }; // ... $c2i = function ($n) { return $n(function ($x) { return $x + 1; }, 0); }; assert($c2i($c0) === 0); assert($c2i($c1) === 1); assert($c2i($c2) === 2); $plus = function ($m, $n) { return function ($s, $z) use ($m, $n) { return $m($s, $n($s, $z)); }; }; assert($c2i($plus($c1, $c2)) === 3); // ...

Slide 34

Slide 34 text

Lambda calculus $true = function ($t, $f) { return $t; }; $false = function ($t, $f) { return $f; }; assert($true ('T', 'F') === 'T'); assert($false('T', 'F') === 'F'); $and = function ($b1, $b2) { return $b1($b2, $b1); }; $or = function ($b1, $b2) { return $b1($b1, $b2); }; assert($and($true, $true) ('T', 'F') === 'T'); assert($and($true, $false) ('T', 'F') === 'F'); assert($and($false, $true) ('T', 'F') === 'F'); assert($and($false, $false)('T', 'F') === 'F'); assert($or($true, $true) ('T', 'F') === 'T'); assert($or($true, $false) ('T', 'F') === 'T'); assert($or($false, $true) ('T', 'F') === 'T'); assert($or($false, $false)('T', 'F') === 'F'); $if = function ($p, $t, $f) { return $p($t, $f); }; assert($if($or($false, $and($true, $true)), 'T', 'F') === 'T');

Slide 35

Slide 35 text

To save our sanity for the rest of the talk we will assume that we have these basic types :)

Slide 36

Slide 36 text

Functions as building blocks

Slide 37

Slide 37 text

Water Juice Beer Wine Marc Claire Adam Nora Ed A B f : A ! B

Slide 38

Slide 38 text

Water Juice Beer Wine Alcoholic Non-Alcoholic Marc Claire Adam Nora Ed A B C f : A ! B g : B ! C

Slide 39

Slide 39 text

Water Juice Beer Wine Alcoholic Non-Alcoholic Marc Claire Adam Nora Ed A B C f : A ! B g : B ! C g f : A ! C

Slide 40

Slide 40 text

Water Juice Beer Wine Alcoholic Non-Alcoholic Marc Claire Adam Nora Ed A B C f : A ! B g : B ! C g f : A ! C Meet little circle, pronounced “after” g f = x ! g ( f ( x ))

Slide 41

Slide 41 text

This is called composition

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

Composition in code

Slide 44

Slide 44 text

$f = function ($s) { return 'f'.$s; }; $g = function ($s) { return 'g'.$s; }; echo $g($f('')); // gf echo $g($f('')); // gf echo $g($f(' !!!')); // gf !!! echo $g($f(' !!!')); // gf !!! Composition

Slide 45

Slide 45 text

Huh, but where is ? g f

Slide 46

Slide 46 text

g f = x ! g ( f ( x )) To express “g little circle f” as a function we need to define little circle.

Slide 47

Slide 47 text

function o($g, $f) { return function ($x) { return $g($f($x)); }; } Composition

Slide 48

Slide 48 text

function o($g, $f) { return function ($x) { return $g($f($x)); }; } $f = function ($s) { return 'f'.$s; }; $g = function ($s) { return 'g'.$s; }; $gf = o($g, $f); Composition g f = x ! g ( f ( x ))

Slide 49

Slide 49 text

function o($g, $f) { return function ($x) { return $g($f($x)); }; } $f = function ($s) { return 'f'.$s; }; $g = function ($s) { return 'g'.$s; }; $gf = o($g, $f); echo $gf(''); // Error... Composition g f = x ! g ( f ( x ))

Slide 50

Slide 50 text

Closure an important implementation detail

Slide 51

Slide 51 text

$y = 1; function f($x) { return $x + $y; } Closure #1

Slide 52

Slide 52 text

$y = 1; function f($x) { return $x + $y; } echo f(1); // Undefined variable: y Closure #1 $y was declared in the outermost scope, aka. the global scope In PHP $y is not defined in this scope. PHP is lexically scoped using functions as boundaries. Inside f there's a new scope that’s local to f.

Slide 53

Slide 53 text

function f() { $y = 1; return function ($x) { return $x + $y; }; } Closure #2

Slide 54

Slide 54 text

function f() { $y = 1; return function ($x) { return $x + $y; }; } echo f()(1); // Undefined variable: y Closure #2 $y is now defined in the scope local to f But we don’t have access to $y inside this lambda, as it has its own scope.

Slide 55

Slide 55 text

function f() { $y = 1; return function ($x) use ($y) { return $x + $y; }; } Closure #3

Slide 56

Slide 56 text

function f() { $y = 1; return function ($x) use ($y) { return $x + $y; }; } echo f()(1); // 2 Closure #3 A free variable whose value gets captured when we call f. In PHP we must explicitly state what we want to capture in from the enclosing scope to achieve closure. This is done by the use() statement. Closure !

Slide 57

Slide 57 text

Fixing little circle…

Slide 58

Slide 58 text

function o($g, $f) { return function ($x) use ($g, $f) { return $g($f($x)); }; } Composition

Slide 59

Slide 59 text

function o($g, $f) { return function ($x) use ($g, $f) { return $g($f($x)); }; } $f = function ($s) { return 'f'.$s; }; $g = function ($s) { return 'g'.$s; }; $gf = o($g, $f); echo $gf(''); // gf echo $gf(''); // gf echo $gf(' !!!'); // gf !!! echo $gf(' !!!'); // gf !!! Composition g f = x ! g ( f ( x ))

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

What about composing functions with multiple arguments?

Slide 62

Slide 62 text

1 + 1 = 2

Slide 63

Slide 63 text

“+” is a binary function ie. it takes 2 arguments. Could call it “f” instead of “+” if we wanted to. + : (N, N) ! N

Slide 64

Slide 64 text

x ! ( y ! x + y ) Or is it a unary function that takes an x and returns a function, that takes an y and returns x + y?

Slide 65

Slide 65 text

1 2 3 x ! 1 + x x ! 2 + x x ! 3 + x ... ... + : N ! N ⇥ N N ⇥ N N

Slide 66

Slide 66 text

This is called currying

Slide 67

Slide 67 text

Currying function normal_plus($x, $y) { return $x + $y; }; echo normal_plus(1, 2); // 3

Slide 68

Slide 68 text

Currying function normal_plus($x, $y) { return $x + $y; }; echo normal_plus(1, 2); // 3 function curried_plus($x) { return function ($y) use ($x) { return $x + $y; }; }; echo curried_plus(1)(2); // 3

Slide 69

Slide 69 text

Currying also means injecting behaviour

Slide 70

Slide 70 text

function plus($x) { return function ($y) use ($x) { return $x + $y; }; }; $addOne = plus(1); echo $addOne(1); // 2 Currying ( y ! 1 + y)(1) = 2 ( x ! ( y ! x + y ))(1) = y ! 1 + y

Slide 71

Slide 71 text

Now we can compose functions with any arity!

Slide 72

Slide 72 text

Currying function o($g, $f) { return function ($x) use ($g, $f) { return $g($f($x)); }; } function plus($x) { return function ($y) use ($x) { return $x + $y; }; }; $addTwo = o(plus(1), plus(1)); echo $addTwo(1); // 3

Slide 73

Slide 73 text

Hm, but little circle still takes two arguments …

Slide 74

Slide 74 text

= g ! ( f ! ( x ! g ( f ( x )))) Function that takes a function g, returns a function that takes a function f, returns a function that takes x and returns g(f(x)).

Slide 75

Slide 75 text

Currying function o($g) { return function ($f) use ($g) { return function ($x) use ($g, $f) { return $g($f($x)); }; }; } function plus(int $x) { return function (int $y) use ($x) { return $x + $y; }; }; echo o(plus(1))(plus(1))(1); // 3 Notice how the number of captured variables grow.

Slide 76

Slide 76 text

Currying is tedious, we need some syntactic sugar

Slide 77

Slide 77 text

Currying $normalPlus = function($x, $y) { return $x + $y; }; $curriedPlus = curry($normalPlus); assert($normalPlus(1, 1) === $curriedPlus(1)(1)); // 2 === 2

Slide 78

Slide 78 text

Currying function curry(callable $fn, ...$args): Closure { return function (...$partialArgs) use ($fn, $args) { return (function ($args) use ($fn) { return count($args) < (new ReflectionFunction($fn)) ->getNumberOfRequiredParameters() ? curry($fn, ...$args) : $fn(...$args); })(array_merge($args, $partialArgs)); }; } $normalPlus = function($x, $y) { return $x + $y; }; $curriedPlus = curry($normalPlus); assert($normalPlus(1, 1) === $curriedPlus(1)(1)); // 2 === 2 Do not panic !

Slide 79

Slide 79 text

Composition has interesting properties

Slide 80

Slide 80 text

f g h A B C D h g : B ! D g f : A ! C

Slide 81

Slide 81 text

f g h A B C D g f : A ! C h g : B ! D h (g f) : A ! D

Slide 82

Slide 82 text

f g h A B C D h g : B ! D g f : A ! C (h g) f : A ! D

Slide 83

Slide 83 text

f g h A B C D h (g f)=(h g) f Function composition is associative g f : A ! C h g : B ! D

Slide 84

Slide 84 text

$o = curry(function ($g, $f) { return function ($x) use ($g, $f) { return $g($f($x)); }; }); $f = function ($s) { return 'f'.$s; }; $g = function ($s) { return 'g'.$s; }; $h = function ($s) { return 'h'.$s; }; $gf = $o($g)($f); // g o f $hg = $o($h)($g); // h o g $h_gf = $o($h)($gf); // h o (g o f) $hg_f = $o($hg)($f); // (h o g) o f // h o (g o f) = (h o g) o f assert($h_gf('') === $hg_f('')); // 'hgf' === 'hgf' Associativity of composition

Slide 85

Slide 85 text

idA : A ! A f : A ! B A B idB : B ! B g : B ! A

Slide 86

Slide 86 text

idA : A ! A f : A ! B A B idB : B ! B g : B ! A f idA : A ! B

Slide 87

Slide 87 text

idA : A ! A f : A ! B A B idB : B ! B g : B ! A idB f : A ! B

Slide 88

Slide 88 text

idA : A ! A f : A ! B A B idB : B ! B g : B ! A g idB : B ! A

Slide 89

Slide 89 text

idA : A ! A f : A ! B A B idB : B ! B g : B ! A idA g : B ! A

Slide 90

Slide 90 text

idA : A ! A f : A ! B A B idB : B ! B g : B ! A f idA = f = idB f g idB = g = idA g The Identity function aka. the function that does nothing

Slide 91

Slide 91 text

$o = curry(function ($g, $f) { return function ($x) use ($g, $f) { return $g($f($x)); }; }); $id = function ($x) { return $x; }; $f = function ($s) { return 'f'.$s; }; $g = function ($s) { return 'g'.$s; }; // f = f o id assert($f('') === $o($f)($id)(‘’)); // 'f' === 'f' // f = id o f assert($f('') === $o($id)($f)(‘’)); // 'f' === ‘f' // g = g o id assert($g('') === $o($g)($id)(‘’)); // 'g' === 'g' // g = id o g assert($g('') === $o($id)($g)(‘’)); // 'g' === 'g' Identity of composition

Slide 92

Slide 92 text

If there is one there is many. Working with lists.

Slide 93

Slide 93 text

Map $map = curry('array_map'); $addOne = function ($x) { return $x + 1; }; $addOneToList = $map($addOne); print_r($addOneToList([1, 2, 3, 4, 5])); // [2, 3, 4, 5, 6]

Slide 94

Slide 94 text

Filter Behaviour first $filter = curry(function ($f, $l) { return array_filter($l, $f); }); $isEven = function ($x) { return $x % 2 === 0; }; $filterEven = $filter($isEven); print_r($filterEven([1, 2, 3, 4, 5])); // [2, 4] Data (the list) last

Slide 95

Slide 95 text

Fold $fold = curry(function ($f, $v, $l) { return array_reduce($l, $f, $v); }); $plus = curry(function ($x, $y) { return $x + $y; }); $sum = $fold($plus)(0); echo $sum([1, 2, 3, 4, 5]); // 15 List Operator Seed value [1, 2, 3, 4, 5] —> ((((0 + 1) + 2) + 3) + 4) + 5 = 15

Slide 96

Slide 96 text

Fold is the essence of recursion.

Slide 97

Slide 97 text

Right fold and Left fold $plus = curry(function ($x, $y) { return $x + $y; }); $sumRight = $foldr($plus)(0); $sumLeft = $foldl($plus)(0); assert($sumRight([1, 2, 3]) === $sumLeft([1, 2, 3])); // 6 = 6 ((0 + 1) + 2) + 3 = 1 + (2 + (3 + 0))

Slide 98

Slide 98 text

Right fold and Left fold $foldr = curry(function ($f, $v, $l) use (&$foldr) { return count($l) > 0 ? $f($l[0])($foldr($f)($v)(array_slice($l, 1))) : $v; }); $foldl = curry(function ($f, $v, $l) use (&$foldl) { return count($l) > 0 ? $foldl($f)($f($v)($l[0]))(array_slice($l, 1)) : $v; }); $plus = curry(function ($x, $y) { return $x + $y; }); $sumRight = $foldr($plus)(0); $sumLeft = $foldl($plus)(0); assert($sumRight([1, 2, 3]) === $sumLeft([1, 2, 3])); // 6 = 6 Tiny white lie for the sake our sanity ((0 + 1) + 2) + 3 = 1 + (2 + (3 + 0))

Slide 99

Slide 99 text

Map and Filter with Fold $map = curry(function ($f, $l) use ($foldr) { return $foldr(curry(function ($x, $v) use ($f) { return array_merge([$f($x)], $v); }))([])($l); }); $filter = curry(function ($p, $l) use ($foldr) { return $foldr(curry(function ($x, $v) use ($p) { return $p($x) ? array_merge([$x], $v) : $v; }))([])($l); }); $even = $filter(function ($x) { return $x % 2 === 0; }); $add1 = $map(function ($x) { return $x + 1; }); $sum = $foldr(curry(function ($x, $y) { return $x + $y; }))(0); assert($sum($add1($even([1, 2, 3, 4]))) === 8);

Slide 100

Slide 100 text

What about composing a list of functions?

Slide 101

Slide 101 text

$id = function ($x) { return $x; }; $o = curry(function ($g, $f) { return function ($x) use ($g, $f) { return $g($f($x)); }; }); $foldr = curry(function ($f, $v, $l) use (&$foldr) { return count($l) > 0 ? $f($l[0])($foldr($f)($v)(array_slice($l, 1))) : $v; }); Composition

Slide 102

Slide 102 text

$id = function ($x) { return $x; }; $o = curry(function ($g, $f) { return function ($x) use ($g, $f) { return $g($f($x)); }; }); $foldr = curry(function ($f, $v, $l) use (&$foldr) { return count($l) > 0 ? $f($l[0])($foldr($f)($v)(array_slice($l, 1))) : $v; }); $compose = $foldr($o)($id); Composition

Slide 103

Slide 103 text

$id = function ($x) { return $x; }; $o = curry(function ($g, $f) { return function ($x) use ($g, $f) { return $g($f($x)); }; }); $foldr = curry(function ($f, $v, $l) use (&$foldr) { return count($l) > 0 ? $f($l[0])($foldr($f)($v)(array_slice($l, 1))) : $v; }); $compose = $foldr($o)($id); $f = function ($s) { return 'f'.$s; }; $g = function ($s) { return 'g'.$s; }; $h = function ($s) { return 'h'.$s; }; $hgf = $compose([$h, $g, $f]); assert($hgf('') === 'hgf'); // 'hgf' === 'hgf' Composition

Slide 104

Slide 104 text

Building things

Slide 105

Slide 105 text

$plus = curry(function ($x, $y) { return $x + $y; }); $sum = $foldr($plus)(0); echo $sum([1, 2, 3, 4, 5]); // 15 Building things #1

Slide 106

Slide 106 text

$plus = curry(function ($x, $y) { return $x + $y; }); $sum = $foldr($plus)(0); echo $sum([1, 2, 3, 4, 5]); // 15 $matrix = [ [1, 1, 1, 1, 1], [2, 2, 2, 2, 2], [3, 3, 3, 3, 3], [4, 4, 4, 4, 4], [5, 5, 5, 5, 5], ]; Building things #1

Slide 107

Slide 107 text

$plus = curry(function ($x, $y) { return $x + $y; }); $sum = $foldr($plus)(0); echo $sum([1, 2, 3, 4, 5]); // 15 $matrix = [ [1, 1, 1, 1, 1], [2, 2, 2, 2, 2], [3, 3, 3, 3, 3], [4, 4, 4, 4, 4], [5, 5, 5, 5, 5], ]; $sumMatrix = $o($sum)($map($sum)); echo $sumMatrix($matrix); // 75 Building things #1

Slide 108

Slide 108 text

$plus = curry(function ($x, $y) { return $x + $y; }); $sum = $foldr($plus)(0); echo $sum([1, 2, 3, 4, 5]); // 15 $matrix = [ [1, 1, 1, 1, 1], [2, 2, 2, 2, 2], [3, 3, 3, 3, 3], [4, 4, 4, 4, 4], [5, 5, 5, 5, 5], ]; $sumMatrix = $o($sum)($map($sum)); echo $sumMatrix($matrix); // 75 $addOneToMatrix = $map($map($plus(1))); echo $sumMatrix($addOneToMatrix($matrix)); // 100 Building things #1

Slide 109

Slide 109 text

$initials = ??? echo $initials('This is rather cool :)'); // T I R C Building things #2

Slide 110

Slide 110 text

$explode = curry('explode'); $implode = curry('implode'); $match = curry('preg_match'); $head = function ($s) { return $s[0]; }; $isWord = $match('/[a-z]/i'); $initials = ??? echo $initials('This is rather cool :)'); // T I R C Building things #2

Slide 111

Slide 111 text

$explode = curry('explode'); $implode = curry('implode'); $match = curry('preg_match'); $head = function ($s) { return $s[0]; }; $isWord = $match('/[a-z]/i'); $initials = $compose([ $implode(' '), $map('strtoupper'), $map($head), $filter($isWord), $explode(' ') ]); echo $initials('This is rather cool :)'); // T I R C Building things #2

Slide 112

Slide 112 text

Building things #2 $explode = curry('explode'); $implode = curry('implode'); $match = curry('preg_match'); $head = function ($s) { return $s[0]; }; $isWord = $match('/[a-z]/i'); $initials = $compose([ $implode(' '), $map($o('strtoupper')($head)), $filter($isWord), $explode(' ') ]); echo $initials('This is rather cool :)'); // T I R C Optimisation for free

Slide 113

Slide 113 text

// Imperative function initials($sentence) { $res = ''; $bits = explode(' ', $sentence); foreach ($bits as $bit) { if (preg_match('/[a-z]/i', $bit)) { $res .= strtoupper($bit[0]).' '; } } return trim($res); } // Functional $initials = $compose([ $implode(' '), $map($o('strtoupper')($head)), $filter($isWord), $explode(' ') ]); Imperative vs Functional

Slide 114

Slide 114 text

How is any of this relevant for a practicing programmer in 2017?

Slide 115

Slide 115 text

• Absence of mutable state makes reasoning simple • Testing is trivial with pure functions • Focus on data and how it flows helps understanding • Composition is a great way to manage complexity • Computation vs effects: functional core/imperative shell Non-exhaustive pros list Non-exhaustive cons list • Requires a different mindset • More difficult to reason about performance

Slide 116

Slide 116 text

Questions? https://github.com/pwm/fp-php-talk (The code from the slides to play with)