Being functional in PHP
David de Boer
functional
functions
y = f(x)
f: X → Y
functional programming
functional thinking
functional communication
not languages
Why should you care?
f(x)
Others
PHPC++
C
Java
– Robert C. Martin
“There is a freight train barreling down the tracks towards us, with multi-core emblazoned on it; and you’d better be ready by the time it gets here.”
λ
closures
__invoke
2001 2009 2011 2012
array_map
array_filter
callable
Slim
Silex
middlewares Symfony2
2013 2014 2015 2016
PSR-7
anonymous classes
foreach
promises
futuresStackPHP
HTTP middleware
proposal
“The limits of my language mean the limits of my world.”
– Ludwig Wittgenstein
I’m David
/ddeboer
@ddeboer_nl
Erlang: The Movie
Erlang
Concurrent
Passing messages
Fault-tolerant
Let’s begin$ brew install erlang
$ erl
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Eshell V7.2.1 (abort with ^G)1>
on OS X
REPL
➊
<?php
$sum = 0;for ($i = 1; $i <= 5; $i++) { $sum = $sum + $i;}
echo $sum;// 15
-module(math).-export([sum/1]).
sum(Number) -> Sum = 0, Numbers = lists:seq(1, Number),
lists:foreach( fun(N) -> Sum = Sum + N end, Numbers ), Sum.
math:sum(5).
no return keyword
** exception error: no match of right hand side value 1
1> X = 5.52> X.53> X = X * 2.** exception error: no match of right hand side value 104> 5 = 10.** exception error: no match of right hand side value 10
but isn’t
looks like assignment
1> lists:sum(lists:seq(1, 5)).15
➋
Imperative<?php
$sum = 0;for ($i = 1; $i <= 5; $i++) { $sum = $sum + $i;}
iteration
keeps changing
-module(math2).-export([sum2/1]).
sum2(Number) -> List = lists:seq(1, Number), sum2(List, 0).
sum2([], Sum) -> Sum;sum2([H|T], Sum) -> sum2(T, H + Sum).
3> math2:sum(5).15
empty list
separate head from tail recurse
pattern matches
generate list
Declarative 1<?php
// Declare a function!function sum($x){ if ($x == 0) { return $x; }
return $x + sum($x - 1);}
sum(5);// still 15
$x never changes
recursion
Declarative 2<?php
function sum2($number){
array_sum(range(1, $number));}
echo sum2(5);// yuuuup, 15 again
functionfunction
composition
Some history
Church Von Neumann
declarative imperative
A lot of our code is about the
hardware it runs on
But programmers should worry about the conceptual problem domain
Recursion-module(math).-export([fac/1]).
fac(0) -> 1;fac(N) -> N * fac(N - 1).
1> math:fac(9).362880
function calls itself
Recursion fail-module(math).-export([fac/1]).
fac(0) -> 1;fac(N) -> N * fac(N - 1).
%%fac(9) = 9 * fac(9 - 1)%% = 9 * 8 * fac(8 - 1)%% = 9 * 8 * 7 * fac(7 - 1)%% = 9 * 8 * 7 * 6 * fac(6 - 1)%% = 9 * 8 * 7 * 6 * 5 * fac(5 -1)%% = 9 * 8 * 7 * 6 * 5 * 4 * fac(4 - 1)%% = 9 * 8 * 7 * 6 * 5 * 4 * 3 * fac(3 - 1)%% = 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * fac(2 - 1)%% = 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 * fac(1 - 1)%% = 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 * 1%% = 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1%% = 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2%% = 9 * 8 * 7 * 6 * 5 * 4 * 6%% = 9 * 8 * 7 * 6 * 5 * 24%% = 9 * 8 * 7 * 6 * 120%% = 9 * 8 * 7 * 720%% = 9 * 8 * 5040%% = 9 * 40320%% = 362880
10 terms in memory
Tail recursion-module(math).-export([tail_fac/1]).
tail_fac(N) -> tail_fac(N, 1).
tail_fac(0, Acc) -> Acc;tail_fac(N, Acc) -> tail_fac(N - 1, N * Acc).
tail_fac(9) = tail_fac(9, 1).tail_fac(9, 1) = tail_fac(9 - 1, 9 * 1).tail_fac(8, 9) = tail_fac(8 - 1, 8 * 9).tail_fac(7, 72) = tail_fac(7 - 1, 7 * 72).tail_fac(6, 504) = tail_fac(6 - 1, 6 * 504).tail_fac(5, 3024) = tail_fac(5 - 1, 5 * 3024).…tail_fac(0, 362880) = 362880
do calculation before recursing
➌
$object->setFoo(5);
$value = $object->getFoo();
no return value
no input value
$object->setFoo(5);
$value = $object->getFoo();
$object->setFoo('Change of state!');
$value = $object->getFoo();
no return value
no input value
same argument, different return value
No side-effects
A pure function
does not rely on data outside itself
does not change data outside itself
Object orientation
Solve problems with objects
Objects have internal state
Modify state through methods
With side-effectsclass Todo{
public $status = 'todo';}
function finish(Todo $task){
$task->status = 'done';}
$uhoh = new Todo();$uhoh2 = $uhoh;
finish($uhoh);
echo $uhoh->status; // doneecho $uhoh2->status; // done???
No side-effectsclass Todo{
public $status = 'todo';}
function finish(Todo $task){
$copy = clone $task;$copy->status = 'done';
return $copy;}
$uhoh = new Todo();$finished = finish($uhoh);
echo $finished->status; // doneecho $uhoh->status; // todo
cloning is cheap
Some state must change
Read/write database
Get user input
Current date and time
Random values (security)
Immutable objects
PSR-7 HTTP messages
Domain value objects
DateTimeImmutable
Service objects
Immutable infrastructure
declarative configuration (Puppet, Chef)
containers (Docker)
➍
Higher-order functions
Functions are values
so they can be arguments
and return values
$names = ['Billy', 'Bob', 'Thornton'];
$anonymise = anonymise('sha256');
var_dump(array_map($anonymise, $names));// array(3) {// [0]=>// string(64) "85eea4a0285dcb11cceb68f39df10d1aa132567dec49b980345142f09f4cb05e"// [1]=>// string(64) "cd9fb1e148ccd8442e5aa74904cc73bf6fb54d1d54d333bd596aa9bb4bb4e961"// [2]=>// string(64) "d7034215823c40c12ec0c7aaff96db94a0e3d9b176f68296eb9d4ca7195c958e"// }
using PHP built-ins
$names = ['Billy', 'Bob', 'Thornton'];
$anonymise = anonymise('sha256');
var_dump(array_map($anonymise, $names));// array(3) {// [0]=>// string(64) "85eea4a0285dcb11cceb68f39df10d1aa132567dec49b980345142f09f4cb05e"// [1]=>// string(64) "cd9fb1e148ccd8442e5aa74904cc73bf6fb54d1d54d333bd596aa9bb4bb4e961"// [2]=>// string(64) "d7034215823c40c12ec0c7aaff96db94a0e3d9b176f68296eb9d4ca7195c958e"// }
function anonymise($algorithm){ return function ($value) use ($algorithm) { return hash($algorithm, $value); };}
higher-order function
closure
using PHP built-ins
function as value
function as argument
Middleware<?php
use Psr\Http\Message\RequestInterface;
function add_header($header, $value){ return function (callable $handler) use ($header, $value) { return function ( RequestInterface $request, array $options ) use ($handler, $header, $value) { $request = $request->withHeader($header, $value);
return $handler($request, $options); }; };}
$myStack = (new MiddlewareStack())->push(add_header('Silly-Header', 'and its value'));
call next in stack
immutable
higher-order function
Middleware<?php
use Psr\Http\Message\ServerRequestInterface as Request;use Psr\Http\Message\ResponseInterface as Response;
$app = new \Slim\App();
$app->add(function (Request $request, Response $response, callable $next) { $response->getBody()->write('Hey there, '); $response = $next($request, $response); $response->getBody()->write('up?');
return $response;});
$app->get('/', function ($request, $response, $args) { $response->getBody()->write('what’s');
return $response;});
$app->run();// Hey there, what’s up?
stream is not immutable
before
after
Typehints?
Middleware<?php
use Psr\Http\Message\RequestInterface as Request;use Psr\Http\Message\ResponseInterface as Response;
$middlewares->add($callback);
class Middlewares{ public function add( callable($request, Response $response, callable $next):Response ) {
}
}
declaration of callable arguments
Middleware<?php
use Psr\Http\Message\RequestInterface as Request;use Psr\Http\Message\ResponseInterface as Response;
$middlewares->add($callback);
class Action implements Middleware{ public function __invoke(Request $request, Response $response, callable $next) { // do something before $next($request, $response); // do something after }}
wrap in class
kapolos/pramda
Middleware<?php
$planets = [ [ "name" => "Earth", "order" => 3, "has" => ["moon", "oreos"], "contact" => [ "name" => "Bob Spongebob", "email" => "[email protected]" ] ], [ "name" => "Mars", "order" => 4, "has" => ["aliens", "rover"], "contact" => [ "name" => "Marvin Martian", "email" => "[email protected]" ] ], // ...];
$nameOfContact = P::compose(P::prop('name'), P::prop('contact'));$getContactNames = P::map($nameOfContact);
// Application$contacts = $getContactNames($planets);P::toArray($contacts); //=> ["Bob Spongebob", "Marvin Martian", ...]
returns a generator
➎
Composition over
inheritance
Single responsibility
and
interface segregation
Service objects<?php
namespace Symfony\Component\Security\Core;
interface SecurityContextInterface{ public function getToken(); public function isGranted($attributes, $object = null);}
Service objects<?php
namespace Symfony\Component\Security\Core\Authentication\Token\Storage;
interface TokenStorageInterface{ public function getToken();}
namespace Symfony\Component\Security\Core\Authorization;
interface AuthorizationCheckerInterface{ public function isGranted($attributes, $object = null);}
single function
Single responsibility
taken to its
logical conclusion
You may not need
all those patterns
Wrap up
➊ No assignment
➋ Declarative
➌ Side-effects ➍ Higher-order functions➎ Patterns
Take-aways
Reduce and isolate side-effects
Create immutable value objects
Be declarative
More?
Top Related