63
Being functional in PHP David de Boer 14 May 2016

Being functional in PHP (PHPDay Italy 2016)

Embed Size (px)

Citation preview

Being functional in PHP

David de Boer14 May 2016

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

“The limits of my language mean the limits of my world.”

– Ludwig Wittgenstein

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

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

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

Composition over

inheritance

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

Take-aways

Reduce and isolate side-effects

Create immutable value objects

Be declarative

More?

Thanks!

https://joind.in/talk/bc26a

@ddeboer_nl