KEMBAR78
Functional Programming in PHP | PDF
Functional Programming
… in PHP
(Warning: May contain traces of Math)
What is
Functional Programming?
Functional Programming is
programming with functions
What is a function?
Marc
Claire
Adam
Nora
Coffee
Tea
Marc
Claire
Adam
Nora
Coffee
Tea
Marc
Claire
Adam
Nora
Coffee
Tea
f : A ! B
A B
How can I model
mathematical functions
in code?
How can I model
mathematical functions
in code?
• Always return the same output value if given
the same input value
How can I model
mathematical functions
in code?
• Always return the same output value if given
the same input value
• Have no side-effects
How can I model
mathematical functions
in code?
• Always return the same output value if given
the same input value
• Have no side-effects
Turns out we do have a name for mathematical functions in
programming… they are called pure functions.
// f :: Int -> Int
function f(int $x): int {
return $x + 1;
}
echo f(2); // 3
echo f(2); // 3
// g :: String -> String
function g(string $s): string {
return $s.'!';
}
echo g('I am pure'); // I am pure!
echo g('I am pure'); // I am pure!
Pure functions
// f :: Int -> Int
function f(int $x): int {
return 1;
}
echo f(4); // 1
echo f(4); // 1
// g :: String -> Bool
function g(string $s): bool {
return $s[0] === 'P';
}
echo g('Pure'); // True
echo g('Pure'); // True
echo g('Impure'); // False
echo g('Impure'); // False
Pure functions
Impure functions
// f :: Int -> Int
function f(int $x): int {
return random_int(0, $x);
}
echo f(10); // 4
echo f(10); // 7
// g :: Int -> Int
function g(int $x): int {
global $y;
return $x + $y;
}
$y = 1;
echo g(2); // 3
$y = 5;
echo g(2); // 7
Impure functions
class User {
private $id = 1;
public function setId(int $id) {
$this->id = $id;
}
public function getId(): int {
return $this->id;
}
}
// h :: User -> Int
function h(User $user): int {
return $user->getId();
}
$user = new User();
echo h($user); // 1
$user->setId(2);
echo h($user); // 2
Impure functions
// f :: String -> String
function f(string $s): string {
echo 'I am not pure anymore :(';
return $s;
}
// g :: String -> String
function g(string $s): string {
file_put_contents(
'/tmp/some.log',
'Impure? Oh no, what have I done?!’
);
return $s;
}
How can I get any real
work done with pure
functions?
• The radical approach
• The pragmatic approach
Why on earth would I want
to use pure functions?
Water
Juice
Beer
Wine
Alcoholic
Non-Alcoholic
Marc
Claire
Adam
Nora
Ed
A B C
f : A ! B g : B ! C
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
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
(g f)(x) = g(f(x))
This is called
composition
How can I express
composition in code?
Let’s talk about a few
ingredients
Functions are first class
citizens, aka …
f(x) = x + 1
We are used to seeing
functions written like this.
f(x) = x + 1
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.
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.
f(x) = x + 1
x ! x + 1
This is called a
lambda function.
f(x) = x + 1
x ! x + 1
And it is just a value.
It can be passed around
just like any other value.
// f returns a function
// f is what is called a higher-order function
function f() {
// our x -> x + 1 lambda function from the previous slide
// at this point it has no label referring to it
return function (int $x): int {
return $x + 1;
};
}
// calling f returns the lambda,
// and is now bound to a label
$labelReferringToTheLambda = f();
// (x -> x + 1)(1) = 2
echo $labelReferringToTheLambda(1); // 2
// array_map() is also a higher-order function
// because it operates on functions.
// In this case the input function is again
// our friend: x -> x + 1
$a = array_map(
function (int $x): int { return $x + 1; },
[1, 2, 3, 4, 5]
);
print_r($a); // [2, 3, 4, 5, 6]
Closure, an important
implementation detail
// y is in the outermost scope, called the global scope
$y = 1;
// PHP has lexical function scope, meaning inside f
// there's a new scope that’s local to f.
function f(int $x): int {
// Sadly in PHP y is not defined in this scope.
// In most other languages with closure support f,
// when called, will have access to y from the
// enclosing scope.
return $x + $y;
}
echo f(2); // Undefined variable: y
// The same in ES6 just works
const y = 1;
const f = x => x + y;
console.log(f(2)); // 3
function f() {
// y is now defined in the scope local to f
$y = 1;
return function (int $x): int {
// Sadly we still don’t have access to y
// inside this lambda function.
return $x + $y;
};
}
echo f()(2); // Undefined variable: y
// Again, in ES6 this just works
const f = () => {
const y = 1;
return x => x + 1;
};
console.log(f()(2)); // 3
function f(): Closure {
$y = 1;
return function (int $x) use ($y): int {
// Finally, our lambda has access to y.
// In PHP we must explicitly state what we
// want to bring in from the enclosing scope
// to achieve a closure.
// This is done by the use() statement.
return $x + $y;
};
}
echo f()(2); // 3
function f(): Closure {
$y = 1;
$f = function (int $x) use ($y): int {
return $x + $y;
};
// y was copied into f so even if we change y
// it doesn't affect our closure
$y = 2;
return $f;
}
echo f()(2); // 3
And now finally back
to…
As long as the
types line up…
Remember:
(g f)(x) = g(f(x))
// f :: String -> String
$f = function (string $s): string { return 'f'.$s; };
// g :: String -> String
$g = function (string $s): string { return 'g'.$s; };
echo $g($f('')); // gf
echo $g($f('')); // gf
echo $g($f(' !!!')); // gf !!!
echo $g($f(' !!!')); // gf !!!
(g f)(x) = g(f(x))
We are really after
this little guy!
// Our very own little circle, which is,
// surprise surprise, itself a function.
// o :: (b -> c) -> (a -> b) -> (a -> c)
function o(callable $g, callable $f): Closure {
return function ($x) use ($g, $f) {
return $g($f($x));
};
}
$f = function (string $s): string { return 'f'.$s; };
$g = function (string $s): string { return 'g'.$s; };
// g o f :: String -> String
$gf = o($g, $f);
echo $gf(''); // gf
echo $gf(''); // gf
echo $gf(' !!!'); // gf !!!
echo $gf(' !!!'); // gf !!!
Function composition has
powerful properties…
f g h
A B C D
h g : B ! D
g f : A ! C
f g h
A B C D
g f : A ! C
h g : B ! D
h (g f) : A ! D
f g h
A B C D
h g : B ! D
g f : A ! C
(h g) f : A ! D
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
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
So we can just get rid of brackets
function o(callable $g, callable $f): Closure {
return function ($x) use ($g, $f) {
return $g($f($x));
};
}
$f = function (string $s): string { return 'f'.$s; };
$g = function (string $s): string { return 'g'.$s; };
$h = function (string $s): string { return 'h'.$s; };
// g o f
$gf = o($g, $f);
// h o g
$hg = o($h, $g);
// h o (g o f)
$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
assert($h_gf('') === $hg_f('')); // 'hgf' === 'hgf'
idA : A ! A
f : A ! B
A B
idB : B ! B
g : B ! A
idA : A ! A
f : A ! B
A B
idB : B ! B
g : B ! A
f idA : A ! B
idA : A ! A
f : A ! B
A B
idB : B ! B
g : B ! A
idB f : A ! B
idA : A ! A
f : A ! B
A B
idB : B ! B
g : B ! A
g idB : B ! A
idA : A ! A
f : A ! B
A B
idB : B ! B
g : B ! A
idA g : B ! A
idA : A ! A
f : A ! B
A B
idB : B ! B
g : B ! A
f idA = f = idB f
g idB = g = idA g
Identity function:
the function that does nothing
function o(callable $g, callable $f): Closure {
return function ($x) use ($g, $f) {
return $g($f($x));
};
}
// id :: a -> a
$id = function ($x) { return $x; };
$f = function (string $s): string { return 'f'.$s; };
$g = function (string $s): string { 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'
We now have the tools to
make a general compose
function
// compose :: [a -> a] -> (a -> a)
function compose(callable ...$fs): Closure {
$f = array_shift($fs);
$composedFs = count($fs) > 0
? compose(...$fs)
: function ($x) { return $x; }; // our friend, id
// our other friend, little circle
return function ($x) use ($f, $composedFs) {
return $f($composedFs($x));
};
}
$f = function (string $s): string { return 'f'.$s; };
$g = function (string $s): string { return 'g'.$s; };
$h = function (string $s): string { return 'h'.$s; };
$hgf = compose($h, $g, $f);
echo $hgf(''); // 'hgf'
What about functions
with multiple arguments?
1 + 1 = 2
+ : (Z, Z) ! Z
x ! ( y ! x + y)
1
2
3
x ! 1 + x
x ! 2 + x
x ! 3 + x
... ...
Z Z ⇥ Z
// plus: x -> y -> x + y
// plus :: Int -> Int -> Int
function plus(int $x): Closure {
return function (int $y) use ($x): int {
return $x + $y;
};
};
// (x -> y -> x + y)(1) = y -> 1 + y
$addOne = plus(1);
// (y -> 1 + y)(1) = 2
echo $addOne(1); // 2
This is called currying
// Let's compose 2 "plus 1" functions
echo compose(plus(1), plus(1))(1); // 3
Let’s write a general
curry function
// curry() takes ordinary functions and returns them curried
function curry(callable $fn, ...$args): Closure {
return function (...$partialArgs) use ($fn, $args) {
$args = array_merge($args, $partialArgs);
$numRequiredArgs = (new ReflectionFunction($fn))
->getNumberOfRequiredParameters();
return count($args) < $numRequiredArgs
? curry($fn, ...$args)
: $fn(...$args);
};
}
// Int -> Int -> Int
$normalPlus = function(int $x, int $y): int { return $x + $y; };
echo $normalPlus(1, 1); // 2
$curriedPlus = curry($normalPlus);
echo $curriedPlus(1, 1); // 2
$addOne = $curriedPlus(1); // x -> 1 + x
echo $addOne(1); // 2
Some useful
Higher-order functions
Map
// map :: (a -> b) -> [a] -> [b]
$map = curry('array_map');
// Int -> Int
$addOne = function (int $x): int { return $x + 1; };
print_r($map($addOne, [1, 2, 3, 4, 5])); // [2, 3, 4, 5, 6]
Filter
// filter :: (a -> bool) -> [a] -> [a]
$filter = curry(function (callable $f, array $a): array {
return array_filter($a, $f);
});
// Int -> Bool
$isEven = function (int $x): bool { return $x % 2 === 0; };
print_r($filter($isEven, [1, 2, 3, 4, 5])); // [2, 4]
Left fold aka foldl
// foldl :: (a -> b -> a) -> a -> [b] -> a
$foldl = curry(function (callable $f, $acc, $head, ...$tail) {
$list = count($tail) > 0
? array_merge([$head], $tail)
: $head;
return array_reduce($list, $f, $acc);
});
// Int -> Int -> Int
$plus = curry(function (int $x, int $y): int { return $x + $y; });
// [Int] -> Int
$sum = $foldl($plus, 0);
echo $sum([1, 2, 3, 4, 5]); // 15
echo $sum(1, 2, 3, 4, 5); // 15
If we have little circle, id
and left fold then we have
compose!
$id = function ($x) { return $x; };
$o = function (callable $g, callable $f): Closure {
return function ($x) use ($g, $f) {
return $g($f($x));
};
};
$foldl = curry(function (callable $f, $acc, $head, ...$tail) {
$list = count($tail) > 0
? array_merge([$head], $tail)
: $head;
return array_reduce($list, $f, $acc);
});
$id = function ($x) { return $x; };
$o = function (callable $g, callable $f): Closure {
return function ($x) use ($g, $f) {
return $g($f($x));
};
};
$foldl = curry(function (callable $f, $acc, $head, ...$tail) {
$list = count($tail) > 0
? array_merge([$head], $tail)
: $head;
return array_reduce($list, $f, $acc);
});
$compose = $foldl($o, $id);
$f = function (string $s): string { return 'f'.$s; };
$g = function (string $s): string { return 'g'.$s; };
$h = function (string $s): string { return 'h'.$s; };
$hgf = $compose($h, $g, $f);
echo $hgf(''); // 'hgf'
Building things
// Int -> Int -> Int
$plus = curry(function (int $x, int $y): int { return $x + $y; });
echo $plus(1, 1); // 2
// Int -> Int -> Int
$plus = curry(function (int $x, int $y): int { return $x + $y; });
echo $plus(1, 1); // 2
// [Int] -> Int
$sum = $foldl($plus, 0);
echo $sum([1, 2, 3, 4, 5]); // 15
// Int -> Int -> Int
$plus = curry(function (int $x, int $y): int { return $x + $y; });
echo $plus(1, 1); // 2
// [Int] -> Int
$sum = $foldl($plus, 0);
echo $sum([1, 2, 3, 4, 5]); // 15
// [[Int]] -> Int
$sumMatrix = $compose($sum, $map($sum));
$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],
];
echo $sumMatrix($matrix); // 75
// Int -> Int -> Int
$plus = curry(function (int $x, int $y): int { return $x + $y; });
echo $plus(1, 1); // 2
// [Int] -> Int
$sum = $foldl($plus, 0);
echo $sum([1, 2, 3, 4, 5]); // 15
// [[Int]] -> Int
$sumMatrix = $compose($sum, $map($sum));
$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],
];
echo $sumMatrix($matrix); // 75
// [[Int]] -> [[Int]]
$addOneToMatrix = $map($map($plus(1)));
echo $sumMatrix($addOneToMatrix($matrix)); // 100
// slice :: String -> String -> [String]
$slice = curry('explode');
// concat :: String -> [String] -> String
$concat = curry('implode');
// concat :: String -> String -> Bool
$match = curry('preg_match');
// slice :: String -> String -> [String]
$slice = curry('explode');
// concat :: String -> [String] -> String
$concat = curry('implode');
// concat :: String -> String -> Bool
$match = curry('preg_match');
// head :: String -> Char
$head = function (string $s): string { return $s[0]; };
// isWord :: String -> Bool
$isWord = $match('/[a-z]/i');
// slice :: String -> String -> [String]
$slice = curry('explode');
// concat :: String -> [String] -> String
$concat = curry('implode');
// concat :: String -> String -> Bool
$match = curry('preg_match');
// head :: String -> Char
$head = function (string $s): string { return $s[0]; };
// isWord :: String -> Bool
$isWord = $match('/[a-z]/i');
// initials :: String -> String
$initials = $compose(
$concat(' '),
$map('strtoupper'),
$map($head),
$filter($isWord),
$slice(' ')
);
echo $initials('This is rather cool :)'); // T I R C
// slice :: String -> String -> [String]
$slice = curry('explode');
// concat :: String -> [String] -> String
$concat = curry('implode');
// concat :: String -> String -> Bool
$match = curry('preg_match');
// head :: String -> Char
$head = function (string $s): string { return $s[0]; };
// isWord :: String -> Bool
$isWord = $match('/[a-z]/i');
// initials :: String -> String
$initials = $compose(
$concat(' '),
$map($compose('strtoupper', $head)),
$filter($isWord),
$slice(' ')
);
echo $initials('This is rather cool :)'); // T I R C
// slice :: String -> String -> [String]
$slice = curry('explode');
// concat :: String -> [String] -> String
$concat = curry('implode');
// concat :: String -> String -> Bool
$match = curry('preg_match');
// head :: String -> Char
$head = function (string $s): string { return $s[0]; };
// isWord :: String -> Bool
$isWord = $match('/[a-z]/i');
// initials :: String -> String
$initials = $compose(
$concat(' '),
$map($compose('strtoupper', $head)),
$filter($isWord),
$slice(' ')
);
echo $initials('This is rather cool :)'); // T I R C
Optimisation for free
Managing complexity
• Composition is the way to create complex
from simple
• Types signatures are interfaces
• Purity enables equational reasoning
Why was FP banished to
academia for so long and
why is it making a comeback?
Thanks for coming
https://github.com/pwm/fp-php-talk
(The code from the slides to play with)
Questions?
https://github.com/pwm/fp-php-talk
(The code from the slides to play with)

Functional Programming in PHP

  • 1.
    Functional Programming … inPHP (Warning: May contain traces of Math)
  • 2.
  • 3.
  • 5.
    What is afunction?
  • 6.
  • 7.
  • 8.
  • 9.
    How can Imodel mathematical functions in code?
  • 10.
    How can Imodel mathematical functions in code? • Always return the same output value if given the same input value
  • 11.
    How can Imodel mathematical functions in code? • Always return the same output value if given the same input value • Have no side-effects
  • 12.
    How can Imodel mathematical functions in code? • Always return the same output value if given the same input value • Have no side-effects Turns out we do have a name for mathematical functions in programming… they are called pure functions.
  • 13.
    // f ::Int -> Int function f(int $x): int { return $x + 1; } echo f(2); // 3 echo f(2); // 3 // g :: String -> String function g(string $s): string { return $s.'!'; } echo g('I am pure'); // I am pure! echo g('I am pure'); // I am pure! Pure functions
  • 14.
    // f ::Int -> Int function f(int $x): int { return 1; } echo f(4); // 1 echo f(4); // 1 // g :: String -> Bool function g(string $s): bool { return $s[0] === 'P'; } echo g('Pure'); // True echo g('Pure'); // True echo g('Impure'); // False echo g('Impure'); // False Pure functions
  • 15.
    Impure functions // f:: Int -> Int function f(int $x): int { return random_int(0, $x); } echo f(10); // 4 echo f(10); // 7 // g :: Int -> Int function g(int $x): int { global $y; return $x + $y; } $y = 1; echo g(2); // 3 $y = 5; echo g(2); // 7
  • 16.
    Impure functions class User{ private $id = 1; public function setId(int $id) { $this->id = $id; } public function getId(): int { return $this->id; } } // h :: User -> Int function h(User $user): int { return $user->getId(); } $user = new User(); echo h($user); // 1 $user->setId(2); echo h($user); // 2
  • 17.
    Impure functions // f:: String -> String function f(string $s): string { echo 'I am not pure anymore :('; return $s; } // g :: String -> String function g(string $s): string { file_put_contents( '/tmp/some.log', 'Impure? Oh no, what have I done?!’ ); return $s; }
  • 18.
    How can Iget any real work done with pure functions?
  • 19.
    • The radicalapproach • The pragmatic approach
  • 20.
    Why on earthwould I want to use pure functions?
  • 21.
  • 22.
  • 23.
  • 24.
  • 26.
    How can Iexpress composition in code?
  • 27.
    Let’s talk abouta few ingredients
  • 28.
    Functions are firstclass citizens, aka …
  • 30.
  • 31.
    We are usedto seeing functions written like this. f(x) = x + 1
  • 32.
    We are usedto 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.
  • 33.
    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.
  • 34.
    f(x) = x+ 1 x ! x + 1 This is called a lambda function.
  • 35.
    f(x) = x+ 1 x ! x + 1 And it is just a value. It can be passed around just like any other value.
  • 36.
    // f returnsa function // f is what is called a higher-order function function f() { // our x -> x + 1 lambda function from the previous slide // at this point it has no label referring to it return function (int $x): int { return $x + 1; }; } // calling f returns the lambda, // and is now bound to a label $labelReferringToTheLambda = f(); // (x -> x + 1)(1) = 2 echo $labelReferringToTheLambda(1); // 2
  • 37.
    // array_map() isalso a higher-order function // because it operates on functions. // In this case the input function is again // our friend: x -> x + 1 $a = array_map( function (int $x): int { return $x + 1; }, [1, 2, 3, 4, 5] ); print_r($a); // [2, 3, 4, 5, 6]
  • 38.
  • 39.
    // y isin the outermost scope, called the global scope $y = 1; // PHP has lexical function scope, meaning inside f // there's a new scope that’s local to f. function f(int $x): int { // Sadly in PHP y is not defined in this scope. // In most other languages with closure support f, // when called, will have access to y from the // enclosing scope. return $x + $y; } echo f(2); // Undefined variable: y
  • 40.
    // The samein ES6 just works const y = 1; const f = x => x + y; console.log(f(2)); // 3
  • 41.
    function f() { //y is now defined in the scope local to f $y = 1; return function (int $x): int { // Sadly we still don’t have access to y // inside this lambda function. return $x + $y; }; } echo f()(2); // Undefined variable: y
  • 42.
    // Again, inES6 this just works const f = () => { const y = 1; return x => x + 1; }; console.log(f()(2)); // 3
  • 43.
    function f(): Closure{ $y = 1; return function (int $x) use ($y): int { // Finally, our lambda has access to y. // In PHP we must explicitly state what we // want to bring in from the enclosing scope // to achieve a closure. // This is done by the use() statement. return $x + $y; }; } echo f()(2); // 3
  • 44.
    function f(): Closure{ $y = 1; $f = function (int $x) use ($y): int { return $x + $y; }; // y was copied into f so even if we change y // it doesn't affect our closure $y = 2; return $f; } echo f()(2); // 3
  • 45.
    And now finallyback to…
  • 47.
    As long asthe types line up…
  • 48.
  • 49.
    // f ::String -> String $f = function (string $s): string { return 'f'.$s; }; // g :: String -> String $g = function (string $s): string { return 'g'.$s; }; echo $g($f('')); // gf echo $g($f('')); // gf echo $g($f(' !!!')); // gf !!! echo $g($f(' !!!')); // gf !!!
  • 50.
    (g f)(x) =g(f(x)) We are really after this little guy!
  • 51.
    // Our veryown little circle, which is, // surprise surprise, itself a function. // o :: (b -> c) -> (a -> b) -> (a -> c) function o(callable $g, callable $f): Closure { return function ($x) use ($g, $f) { return $g($f($x)); }; } $f = function (string $s): string { return 'f'.$s; }; $g = function (string $s): string { return 'g'.$s; }; // g o f :: String -> String $gf = o($g, $f); echo $gf(''); // gf echo $gf(''); // gf echo $gf(' !!!'); // gf !!! echo $gf(' !!!'); // gf !!!
  • 52.
  • 53.
    f g h AB C D h g : B ! D g f : A ! C
  • 54.
    f g h AB C D g f : A ! C h g : B ! D h (g f) : A ! D
  • 55.
    f g h AB C D h g : B ! D g f : A ! C (h g) f : A ! D
  • 56.
    f g h AB C D h (g f)=(h g) f Function composition is associative g f : A ! C h g : B ! D
  • 57.
    f g h AB C D h (g f)=(h g) f Function composition is associative g f : A ! C h g : B ! D So we can just get rid of brackets
  • 58.
    function o(callable $g,callable $f): Closure { return function ($x) use ($g, $f) { return $g($f($x)); }; } $f = function (string $s): string { return 'f'.$s; }; $g = function (string $s): string { return 'g'.$s; }; $h = function (string $s): string { return 'h'.$s; }; // g o f $gf = o($g, $f); // h o g $hg = o($h, $g); // h o (g o f) $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 assert($h_gf('') === $hg_f('')); // 'hgf' === 'hgf'
  • 59.
    idA : A! A f : A ! B A B idB : B ! B g : B ! A
  • 60.
    idA : A! A f : A ! B A B idB : B ! B g : B ! A f idA : A ! B
  • 61.
    idA : A! A f : A ! B A B idB : B ! B g : B ! A idB f : A ! B
  • 62.
    idA : A! A f : A ! B A B idB : B ! B g : B ! A g idB : B ! A
  • 63.
    idA : A! A f : A ! B A B idB : B ! B g : B ! A idA g : B ! A
  • 64.
    idA : A! A f : A ! B A B idB : B ! B g : B ! A f idA = f = idB f g idB = g = idA g Identity function: the function that does nothing
  • 65.
    function o(callable $g,callable $f): Closure { return function ($x) use ($g, $f) { return $g($f($x)); }; } // id :: a -> a $id = function ($x) { return $x; }; $f = function (string $s): string { return 'f'.$s; }; $g = function (string $s): string { 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'
  • 66.
    We now havethe tools to make a general compose function
  • 67.
    // compose ::[a -> a] -> (a -> a) function compose(callable ...$fs): Closure { $f = array_shift($fs); $composedFs = count($fs) > 0 ? compose(...$fs) : function ($x) { return $x; }; // our friend, id // our other friend, little circle return function ($x) use ($f, $composedFs) { return $f($composedFs($x)); }; } $f = function (string $s): string { return 'f'.$s; }; $g = function (string $s): string { return 'g'.$s; }; $h = function (string $s): string { return 'h'.$s; }; $hgf = compose($h, $g, $f); echo $hgf(''); // 'hgf'
  • 69.
    What about functions withmultiple arguments?
  • 70.
    1 + 1= 2
  • 71.
    + : (Z,Z) ! Z
  • 72.
    x ! (y ! x + y)
  • 73.
    1 2 3 x ! 1+ x x ! 2 + x x ! 3 + x ... ... Z Z ⇥ Z
  • 74.
    // plus: x-> y -> x + y // plus :: Int -> Int -> Int function plus(int $x): Closure { return function (int $y) use ($x): int { return $x + $y; }; }; // (x -> y -> x + y)(1) = y -> 1 + y $addOne = plus(1); // (y -> 1 + y)(1) = 2 echo $addOne(1); // 2
  • 75.
  • 76.
    // Let's compose2 "plus 1" functions echo compose(plus(1), plus(1))(1); // 3
  • 77.
    Let’s write ageneral curry function
  • 78.
    // curry() takesordinary functions and returns them curried function curry(callable $fn, ...$args): Closure { return function (...$partialArgs) use ($fn, $args) { $args = array_merge($args, $partialArgs); $numRequiredArgs = (new ReflectionFunction($fn)) ->getNumberOfRequiredParameters(); return count($args) < $numRequiredArgs ? curry($fn, ...$args) : $fn(...$args); }; } // Int -> Int -> Int $normalPlus = function(int $x, int $y): int { return $x + $y; }; echo $normalPlus(1, 1); // 2 $curriedPlus = curry($normalPlus); echo $curriedPlus(1, 1); // 2 $addOne = $curriedPlus(1); // x -> 1 + x echo $addOne(1); // 2
  • 79.
  • 80.
    Map // map ::(a -> b) -> [a] -> [b] $map = curry('array_map'); // Int -> Int $addOne = function (int $x): int { return $x + 1; }; print_r($map($addOne, [1, 2, 3, 4, 5])); // [2, 3, 4, 5, 6]
  • 81.
    Filter // filter ::(a -> bool) -> [a] -> [a] $filter = curry(function (callable $f, array $a): array { return array_filter($a, $f); }); // Int -> Bool $isEven = function (int $x): bool { return $x % 2 === 0; }; print_r($filter($isEven, [1, 2, 3, 4, 5])); // [2, 4]
  • 82.
    Left fold akafoldl // foldl :: (a -> b -> a) -> a -> [b] -> a $foldl = curry(function (callable $f, $acc, $head, ...$tail) { $list = count($tail) > 0 ? array_merge([$head], $tail) : $head; return array_reduce($list, $f, $acc); }); // Int -> Int -> Int $plus = curry(function (int $x, int $y): int { return $x + $y; }); // [Int] -> Int $sum = $foldl($plus, 0); echo $sum([1, 2, 3, 4, 5]); // 15 echo $sum(1, 2, 3, 4, 5); // 15
  • 83.
    If we havelittle circle, id and left fold then we have compose!
  • 84.
    $id = function($x) { return $x; }; $o = function (callable $g, callable $f): Closure { return function ($x) use ($g, $f) { return $g($f($x)); }; }; $foldl = curry(function (callable $f, $acc, $head, ...$tail) { $list = count($tail) > 0 ? array_merge([$head], $tail) : $head; return array_reduce($list, $f, $acc); });
  • 85.
    $id = function($x) { return $x; }; $o = function (callable $g, callable $f): Closure { return function ($x) use ($g, $f) { return $g($f($x)); }; }; $foldl = curry(function (callable $f, $acc, $head, ...$tail) { $list = count($tail) > 0 ? array_merge([$head], $tail) : $head; return array_reduce($list, $f, $acc); }); $compose = $foldl($o, $id); $f = function (string $s): string { return 'f'.$s; }; $g = function (string $s): string { return 'g'.$s; }; $h = function (string $s): string { return 'h'.$s; }; $hgf = $compose($h, $g, $f); echo $hgf(''); // 'hgf'
  • 86.
  • 87.
    // Int ->Int -> Int $plus = curry(function (int $x, int $y): int { return $x + $y; }); echo $plus(1, 1); // 2
  • 88.
    // Int ->Int -> Int $plus = curry(function (int $x, int $y): int { return $x + $y; }); echo $plus(1, 1); // 2 // [Int] -> Int $sum = $foldl($plus, 0); echo $sum([1, 2, 3, 4, 5]); // 15
  • 89.
    // Int ->Int -> Int $plus = curry(function (int $x, int $y): int { return $x + $y; }); echo $plus(1, 1); // 2 // [Int] -> Int $sum = $foldl($plus, 0); echo $sum([1, 2, 3, 4, 5]); // 15 // [[Int]] -> Int $sumMatrix = $compose($sum, $map($sum)); $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], ]; echo $sumMatrix($matrix); // 75
  • 90.
    // Int ->Int -> Int $plus = curry(function (int $x, int $y): int { return $x + $y; }); echo $plus(1, 1); // 2 // [Int] -> Int $sum = $foldl($plus, 0); echo $sum([1, 2, 3, 4, 5]); // 15 // [[Int]] -> Int $sumMatrix = $compose($sum, $map($sum)); $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], ]; echo $sumMatrix($matrix); // 75 // [[Int]] -> [[Int]] $addOneToMatrix = $map($map($plus(1))); echo $sumMatrix($addOneToMatrix($matrix)); // 100
  • 91.
    // slice ::String -> String -> [String] $slice = curry('explode'); // concat :: String -> [String] -> String $concat = curry('implode'); // concat :: String -> String -> Bool $match = curry('preg_match');
  • 92.
    // slice ::String -> String -> [String] $slice = curry('explode'); // concat :: String -> [String] -> String $concat = curry('implode'); // concat :: String -> String -> Bool $match = curry('preg_match'); // head :: String -> Char $head = function (string $s): string { return $s[0]; }; // isWord :: String -> Bool $isWord = $match('/[a-z]/i');
  • 93.
    // slice ::String -> String -> [String] $slice = curry('explode'); // concat :: String -> [String] -> String $concat = curry('implode'); // concat :: String -> String -> Bool $match = curry('preg_match'); // head :: String -> Char $head = function (string $s): string { return $s[0]; }; // isWord :: String -> Bool $isWord = $match('/[a-z]/i'); // initials :: String -> String $initials = $compose( $concat(' '), $map('strtoupper'), $map($head), $filter($isWord), $slice(' ') ); echo $initials('This is rather cool :)'); // T I R C
  • 94.
    // slice ::String -> String -> [String] $slice = curry('explode'); // concat :: String -> [String] -> String $concat = curry('implode'); // concat :: String -> String -> Bool $match = curry('preg_match'); // head :: String -> Char $head = function (string $s): string { return $s[0]; }; // isWord :: String -> Bool $isWord = $match('/[a-z]/i'); // initials :: String -> String $initials = $compose( $concat(' '), $map($compose('strtoupper', $head)), $filter($isWord), $slice(' ') ); echo $initials('This is rather cool :)'); // T I R C
  • 95.
    // slice ::String -> String -> [String] $slice = curry('explode'); // concat :: String -> [String] -> String $concat = curry('implode'); // concat :: String -> String -> Bool $match = curry('preg_match'); // head :: String -> Char $head = function (string $s): string { return $s[0]; }; // isWord :: String -> Bool $isWord = $match('/[a-z]/i'); // initials :: String -> String $initials = $compose( $concat(' '), $map($compose('strtoupper', $head)), $filter($isWord), $slice(' ') ); echo $initials('This is rather cool :)'); // T I R C Optimisation for free
  • 96.
    Managing complexity • Compositionis the way to create complex from simple • Types signatures are interfaces • Purity enables equational reasoning
  • 97.
    Why was FPbanished to academia for so long and why is it making a comeback?
  • 98.
  • 99.