Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional Programming in PHP

Functional Programming in PHP

It’s becoming increasingly difficult to ignore the term "Functional Programming" while reading online discussions about programming languages or programming in general. People talking about FP routinely use terms that can be strange and unfamiliar to developers coming from the imperative world.

So what exactly is Functional Programming? Where does it come from? How does it differ from the programming I already know?

This talk will try to answer these questions by weaving seemingly unrelated concepts into a (hopefully) coherent story of FP, using PHP as the vehicle of expressing these ideas. The side effect (pun intended) of having a grasp of FP will make you a better developer.

Video:
https://www.youtube.com/watch?v=pWQV1x3YIJM&t=1m0s

Code:
https://github.com/pwm/fp-php-talk

0bbde64faf84c284935b1a58876435ca?s=128

Zsolt Szende

June 01, 2017
Tweet

More Decks by Zsolt Szende

Other Decks in Programming

Transcript

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

  2. What is Functional Programming?

  3. Functional Programming is programming with functions.

  4. None
  5. What is a function?

  6. Marc Claire Adam Nora Coffee Tea A B

  7. Marc Claire Adam Nora Coffee Tea A B

  8. Marc Claire Adam Nora Coffee Tea A B

  9. Marc Claire Adam Nora Coffee Tea f : A !

    B A B Functions, like in mathematics
  10. Functions in code

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

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

    input. Rule #2 Do nothing else aka. no side effects.
  13. Turns out we do have a name for functions in

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

    programming. We call them pure functions
  15. // 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.
  16. // 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
  17. 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
  18. // 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
  19. What can we do if we only have functions?

  20. None
  21. f ( x ) = x + 1

  22. We are used to seeing functions written like this. f

    ( x ) = x + 1
  23. 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.
  24. 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.
  25. f ( x ) = x + 1 x !

    x + 1 This is called a lambda expression.
  26. function f() { return function ($x) { return $x +

    1; }; } Lambdas
  27. 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
  28. 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
  29. 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…
  30. You have just witnessed Higher-order functions

  31. You said only functions, but there was + and 1

  32. Alonzo Church

  33. 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); // ...
  34. 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');
  35. To save our sanity for the rest of the talk

    we will assume that we have these basic types :)
  36. Functions as building blocks

  37. Water Juice Beer Wine Marc Claire Adam Nora Ed A

    B f : A ! B
  38. Water Juice Beer Wine Alcoholic Non-Alcoholic Marc Claire Adam Nora

    Ed A B C f : A ! B g : B ! C
  39. 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
  40. 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 ))
  41. This is called composition

  42. None
  43. Composition in code

  44. $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
  45. Huh, but where is ? g f

  46. g f = x ! g ( f ( x

    )) To express “g little circle f” as a function we need to define little circle.
  47. function o($g, $f) { return function ($x) { return $g($f($x));

    }; } Composition
  48. 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 ))
  49. 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 ))
  50. Closure an important implementation detail

  51. $y = 1; function f($x) { return $x + $y;

    } Closure #1
  52. $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.
  53. function f() { $y = 1; return function ($x) {

    return $x + $y; }; } Closure #2
  54. 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.
  55. function f() { $y = 1; return function ($x) use

    ($y) { return $x + $y; }; } Closure #3
  56. 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 !
  57. Fixing little circle…

  58. function o($g, $f) { return function ($x) use ($g, $f)

    { return $g($f($x)); }; } Composition
  59. 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 ))
  60. None
  61. What about composing functions with multiple arguments?

  62. 1 + 1 = 2

  63. “+” is a binary function ie. it takes 2 arguments.

    Could call it “f” instead of “+” if we wanted to. + : (N, N) ! N
  64. 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?
  65. 1 2 3 x ! 1 + x x !

    2 + x x ! 3 + x ... ... + : N ! N ⇥ N N ⇥ N N
  66. This is called currying

  67. Currying function normal_plus($x, $y) { return $x + $y; };

    echo normal_plus(1, 2); // 3
  68. 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
  69. Currying also means injecting behaviour

  70. 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
  71. Now we can compose functions with any arity!

  72. 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
  73. Hm, but little circle still takes two arguments …

  74. = 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)).
  75. 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.
  76. Currying is tedious, we need some syntactic sugar

  77. Currying $normalPlus = function($x, $y) { return $x + $y;

    }; $curriedPlus = curry($normalPlus); assert($normalPlus(1, 1) === $curriedPlus(1)(1)); // 2 === 2
  78. 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 !
  79. Composition has interesting properties

  80. f g h A B C D h g :

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

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

    B ! D g f : A ! C (h g) f : A ! D
  83. 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
  84. $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
  85. idA : A ! A f : A ! B

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

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

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

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

    A B idB : B ! B g : B ! A idA g : B ! A
  90. 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
  91. $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
  92. If there is one there is many. Working with lists.

  93. 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]
  94. 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
  95. 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
  96. Fold is the essence of recursion.

  97. 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))
  98. 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))
  99. 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);
  100. What about composing a list of functions?

  101. $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
  102. $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
  103. $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
  104. Building things

  105. $plus = curry(function ($x, $y) { return $x + $y;

    }); $sum = $foldr($plus)(0); echo $sum([1, 2, 3, 4, 5]); // 15 Building things #1
  106. $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
  107. $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
  108. $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
  109. $initials = ??? echo $initials('This is rather cool :)'); //

    T I R C Building things #2
  110. $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
  111. $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
  112. 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
  113. // 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
  114. How is any of this relevant for a practicing programmer

    in 2017?
  115. • 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
  116. Questions? https://github.com/pwm/fp-php-talk (The code from the slides to play with)