Why async and functional programming in PHP7 suck and how to get overr it?

Preview:

Citation preview

Witek Adamus

DocPlanner 2017, Warszawa

Why async and functional programming in PHP7 suck and how to get over it?

Who am I?

5 years of

imperative

programming

2 years of

functional

programming

Back to

PHP7

❏ WHY?❏ What can functional programming bring to the table?

❏ WHAT?❏ When language can be described as functional?

❏ PROBLEMS?❏ Top sins of PHP

❏ SOLUTION?

Table of content

WHY?

❏ Higher quality?❏ Speed / Scalability❏ Less bugs❏ Easy to reason about❏ Predictability

WHY?

WHY?

Pros Cons

Efficiency ???

Entry threshold ???

Mathematical description of

reality

???

WHY?

Pros Cons In result

Efficiency Efficiency Scalability / Quality

Entry threshold Entry threshold Let’s get the party started

Mathematical description of

reality

Mathematical description of

reality

Shorter and more descriptive code

WHY?

Pros Cons In result

Efficiency Efficiency Scalability / Quality

Entry threshold Entry threshold Let’s get the party started

Mathematical description of

reality

Mathematical description of

reality

Shorter and more descriptive code

INVEST AND WAITFOR A DIVIDEND

public function someMethodWithMisleadingName( array $mysteriousInput){ $arr = []; foreach ($mysteriousInput as $inp) { if ($inp > 10) { $arr[] = $inp; } } $arrLeft = $arrRight = $arrCenter = []; foreach ($arr as $elem) { $bucket = $elem <=> 20; if ($bucket < 0) { $arrLeft[] = $elem; } if ($bucket == 0) { $arrCenter[] = $elem; } if ($bucket > 0) { $arrRight[] = $elem;} } $countLeft = count($arrLeft); $countCenter = count($arrCenter); $countRight = count($arrRight); return array_sum([$countLeft, $countCenter, $countRight]) / 3;}

public function someMethodWithMisleadingName( array $mysteriousInput) { $arr = []; foreach ($mysteriousInput as $inp) { if ($inp > 10) { $arr[] = $inp; } } $arrLeft = $arrRight = $arrCenter = []; foreach ($arr as $elem) { $bucket = $elem <=> 20; if ($bucket < 0) { $arrLeft[] = $elem; } if ($bucket == 0) { $arrCenter[] = $elem; } if ($bucket > 0) { $arrRight[] = $elem;} } $countLeft = count($arrLeft); $countCenter = count($arrCenter); $countRight = count($arrRight); return array_sum(

[$countLeft, $countCenter, $countRight]) / 3;

}

public function someMethodWithMisleadingName( ParallelListCollection $mysteriousInput) { return $mysteriousInput

->filter(function ($elem) {return $elem > 10;

})->partition(function ($elem) {

return $elem <=> 20;})->map(function ($bucket) {

return $bucket->count();})->avg();

}

public function someMethodWithMisleadingName( array $mysteriousInput) { $arr = []; foreach ($mysteriousInput as $inp) { if ($inp > 10) { $arr[] = $inp; } } $arrLeft = $arrRight = $arrCenter = []; foreach ($arr as $elem) { $bucket = $elem <=> 20; if ($bucket < 0) { $arrLeft[] = $elem; } if ($bucket == 0) { $arrCenter[] = $elem; } if ($bucket > 0) { $arrRight[] = $elem;} } $countLeft = count($arrLeft); $countCenter = count($arrCenter); $countRight = count($arrRight); return array_sum(

[$countLeft, $countCenter, $countRight]) / 3;

}

public function someMethodWithMisleadingName( ParallelListCollection $mysteriousInput) { return $mysteriousInput

->filter(function ($elem) {return $elem > 10;

})->partition(function ($elem) {

return $elem <=> 20;})->map(function ($bucket) {

return $bucket->count();})->avg();

}

WHAT?

❏ Function is a first-class citizen

❏ Lambda Calculus

❏ Immutability

❏ No side effects

First-class citizen

❏ Can be stored in variable and data structures

❏ Can be passed as a parameter to procedure/functions

❏ Can be returned by procedures/functions

❏ Can be instantiated inline

❏ Has it’s own identity (name independent)

Lambda Calculus

ƛ(x) = z

Lambda Calculus

ƛ(x) = z❏ Higher-order functions❏ Currying

Lambda Calculus

ƛ(x) = z❏ Higher-order functions❏ Currying

SWITCH YOUR THINKING

FROM “HOW?” TO “WHAT?”

No side effects? Immutability?

:(

Functional vs Object oriented programming

?

PHP7 is functional

…but is dirty and salty as well

What do I miss in PHP7 that Scala luckily has?

❏ Immutability by default❏ Objects cloning❏ Options❏ Either❏ Future❏ Parallel collections❏ Tail recurrency

❏ Generic types❏ Arrow functions❏ Pattern matching / case classes

https://github.com/php-slang/php-slang

http://phpslang.io

# composer require php-slang/php-slang

Few rules to make your code functional

Do not use

❏ reassignments❏ if❏ null❏ for❏ foreach

Quick pool

❏ Do you DDD?

Quick pool

❏ Do you DDD?❏ Do you use setters?

Quick pool

❏ Do you DDD?❏ Do you use setters?

“VALUE OBJECTS are completely immutable.”Eric Evans

Do not use

❏ reassignments

$bigHouse = new Building(5);

$bigHouse = new Building(5);$smallerHouse = $bigHouse->setFloors(2);

echo $bigHouse->getFloors(); echo $smallerHouse->getFloors();

$bigHouse = new Building(5);$smallerHouse = $bigHouse->setFloors(2);

echo $bigHouse->getFloors(); //2echo $smallerHouse->getFloors(); //2

class Building{ protected $floors;

public function __construct(int $floors) { $this->setFloors($floors); }

public function getFloors() : int { return $this->floors; }

public function setFloors(int $floors) : Building { $this->floors = $floors; return $this; }}

use PhpSlang\Util\Copy;

class Building{ use Copy;

protected $floors;

public function __construct(int $floors) { $this->floors = $floors; }

public function getFloors() : int { return $this->floors; }

public function withFloors(int $floors) : Building { return $this->copy('floors', $floors); }}

$bigHouse = new Building(5);$smallerHouse = $bigHouse->withFloors(2);

echo $bigHouse->getFloors(); //5echo $smallerHouse->getFloors(); //2

$bigHouse = new Building(5);$smallerHouse = $bigHouse->withFloors(2);

echo $bigHouse->getFloors(); //5echo $smallerHouse->getFloors(); //2

REFERENTIAL TRANSPARENCY

Referential transparency

❏ f(x, y) = (x+y) * y

Referential transparency

❏ f(x, y) = (x+y) * y

x, y and a result can be a function as well

Referential transparency

❏ f(x, y) = (x+y) * y

Eg.

❏ f(2, 4) = (2 + 4) * 4 = 8 * 4 = 32

❏ f(1, 8) = (1 + 8) * 8 = 9 * 8 = 72

Referential transparency

❏ f(x, y) = (x+y) * y

Eg.

❏ f(2, 4) = (2 + 4) * 4 = 8 * 4 = 32

❏ f(1, 8) = (1 + 8) * 8 = 9 * 8 = 72

i = 71;ComputingService::_opCount++;comps.push(new Tuple2(x, y));

Referential transparency

❏ f(x, y) = (x+y) * y

Eg.

❏ f(2, 4) = (2 + 4) * 4 = 8 * 4 = 32

❏ f(1, 8) = (1 + 8) * 8 = 9 * 8 = 72

i = 71;ComputingService::_opCount++;comps.push(new Tuple2(x, y));

Few rules to make your code functional

Do not use

:) reassignments❏ if❏ null❏ for❏ foreach

Do not use

:) reassignments❏ if❏ null❏ for❏ foreach

?

Do not use

:) reassignments❏ if❏ null❏ for❏ foreach

Option

OptionMonad which may contain something or nothing

What is a monad?

What is a monad?

What is a monad?

What is a monad?

What is a monad?

map

What is a monad?

flatMap

What is a monad?

Option

Option

NoneSome

Option

Option

NoneSomemap(Closure $expression)getOrElse($default)

map(Closure $expression)getOrElse($default)

Option

Option

NoneSomemap(Closure $expression)getOrElse($default)

map(Closure $expression)getOrElse($default)

Some($expression($this->content)

$this->content

Option

Option

NoneSomemap(Closure $expression)getOrElse($default)

map(Closure $expression)getOrElse($default)

None

$default

public function findByEmail(string $email) : User{ $user = $this->findOneBy(['email' => $email]); if (!$user instanceof User) { throw new NotFoundException("oh my"); } return $user;}

try { return new Response(findByEmail($email), HTTP_OK);} catch (NotFoundException $exception) { return new Response([], HTTP_NOT_FOUND);}

public function findByEmail(string $email) : User{ $user = $this->findOneBy(['email' => $email]); if (!$user instanceof User) { throw new NotFoundException("oh my"); } return $user;}

try { return new Response(findByEmail($email), HTTP_OK);} catch (NotFoundException $exception) { return new Response([], HTTP_NOT_FOUND);}

public function findByEmail(string $email) : Option{ return Option::of($this->findOneBy(['email' => $email]));}

return findByEmail($email)) ->map(function (User $user) { return new Response($user, HTTP_OK); }) ->getOrElse(new Response('', HTTP_NOT_FOUND));

public function findByEmail(string $email) : User{ $user = $this->findOneBy(['email' => $email]); if (!$user instanceof User) { throw new NotFoundException("oh my"); } return $user;}

try { return new Response(findByEmail($email), HTTP_OK);} catch (NotFoundException $exception) { return new Response([], HTTP_NOT_FOUND);}

public function findByEmail(string $email) : Option{ return Option::of($this->findOneBy(['email' => $email]));}

return findByEmail($email)) ->map(function (User $user) { return new Response($user, HTTP_OK); }) ->getOrElse(new Response('', HTTP_NOT_FOUND));

map

OptionHow about nesting

public function findByEmail(string $email) : Option{ return Option::of($this->findOneBy(['email' => $email]));}

public function postalCode(string $email, PostalCode $default) : PostalCode{

return findByEmail($email)->map(function (User $user) {

return $user->getProfile()->getAdress()->getPostalCode();})->getOrElse($default);

}

public function postalCode(string $email, PostalCode $default) : PostalCode{

return findByEmail($email);->map(function (User $user) {

return $user->getProfile()->getAdress()->getPostalCode();})->getOrElse($default);

}

public function postalCode(string $email, PostalCode $default) : PostalCode{

return findByEmail($email)->flatMap(function (User $user) {

return $user->getProfile();})

->flatMap(function (UserProfile $userProfile) {return $userProfile->getAdress();

}) ->flatMap(function (Adress $adress) {

return $adress->getPostalCode();})->getOrElse($default);

}

public function postalCode(string $email, PostalCode $default) : PostalCode{

return findByEmail($email) ->flatMap(function (User $user) {

return $user->getProfile();})

->flatMap(function (UserProfile $userProfile) {return $userProfile->getAdress();

}) ->flatMap(function (Adress $adress) {

return $adress->getPostalCode();})->getOrElse($default);

}

flatMap

Few rules to make your code functional

Do not use

:) reassignments:) if:) null❏ for❏ foreach

Shortened notation for anonymous functions

public function displayNameForUser(string $email) : string{ return $this ->userRepository ->findByEmail($email) ->flatMap(function (User $user) { return $user->getFirstName(); }) ->getOrCall(function () use (string $email) : string {

return $this->getSomethingElseWith($email);});

}

public function displayNameForUser(string $email) : string{ return $this ->userRepository ->findByEmail($email) ->flatMap(function (User $user) { return $user->getFirstName(); }) ->getOrCall(function () use (string $email) : string {

return $this->getSomethingElseWith($email);});

}

public displayNameForUser(string email) : stringuserRepository

->findByEmail(email)->flatMap(_->getFirstName)->getOrCall(getSomethingElseWith(email))

:(

public def displayNameForUser(email : string) : string =userRepository

.findByEmail(email)

.flatMap(_.getFirstName)

.getOrElse(_ => getSomethingElseWith(email))\:)

public function displayNameForUser(string $email) : string{ return $this ->userRepository ->findByEmail($email) ->flatMap(Extract($user)->getFirstName()) ->getOrCall(Extract($this)->getSomethingElseWith($email));}

NYI

\:)

Generic types

public function maybeSomething(string $email) : Option{ ...}

/*** @return Option<string>*/public function maybeSomething(string $email) : Option{ ...}

https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md

:(

public function maybeSomething(string $email) : Option<string>{ ...}

:o

public function maybeSomething(string $email) : Option<string>{ ...}

❏ Version: 0.4.0❏ Date: 2016-01-06❏ Author:

❏ Ben Scholzen 'DASPRiD' mail@dasprids.de,❏ Rasmus Schultz rasmus@mindplay.dk

❏ Status: Draft❏ First Published at: http://wiki.php.net/rfc/generics

:(

public function maybeSomething(string $email) : Option<string>{ ...}

WANT TO EARN EXTRA $1020 ?https://www.bountysource.com/issues/20553561-add-generics-support:~|

Syntax doesn’t matterthinking does

Erlang guys

What do I miss in PHP7 that Scala luckily has?

:/ Immutability by default:) Objects cloning:) Options❏ Either❏ Future❏ Parallel collections❏ Tail recurrency

:( Generic types:( Arrow functions❏ Pattern matching / case classes

Either

Either

Either

RightLeft

Either

Either

RightLeftleft(Closure $expr): Eitherright(Closure $expr): Eitherget()

left(Closure $expr): Eitherright(Closure $expr): Eitherget()

Either

Either

RightLeftleft(Closure $expr): Eitherright(Closure $expr): Eitherget()

left(Closure $expr): Eitherright(Closure $expr): Eitherget()

SEMANTICS!

What do I miss in PHP7 that Scala luckily has?

:/ Immutability by default:) Objects cloning:) Options:) Either❏ Future❏ Parallel collections❏ Tail recurrency

:( Generic types:( Arrow functions❏ Pattern matching / case classes

Pattern Matching

Pattern matching

Result

Variant 1

Variant 2

Variant 3

Variant N

...

return (Match::of($someKindResult))->match(

new Case(IvalidInput::class, new Response('', HTTP_BAD_REQUEST)),new Case(NotFound::class, new Response('',HTTP_NOT_FOUND),new Default(function () use ($someKindResult) {

return new Response($someKindResult, HTTP_OK);})

);

What do I miss in PHP7 that Scala luckily has?

:/ Immutability by default:) Objects cloning:) Options:) Either❏ Future❏ Parallel collections❏ Tail recurrency

:( Generic types:( Arrow functions:)/:( Pattern matching / case classes

Parallel collections

Parallel collections

Collection

N...3210

public function beautifulMultiplyOddsBy( array $input, float $multiplication) : ListCollection{ return (new ListCollection($input)) ->filter(function ($number) { return $number % 2 !== 0; }) ->map(function ($number) use ($multiplication) { return $number * $multiplication; });}

public function accumulatedText(array $words) : string { $text = ''; foreach ($words as $word) { $text .= $word . ' '; } return $text;}

public function accumulatedText(array $words) : string { return (new ListCollection($words)) ->fold('', function (string $acumulator, string $word) { return $acumulator . $word . ' '; });}

(new ListCollection([1,2,3,4]))->tail(); //ListCollection([2,3,4])

(new ListCollection([1,2,3,4]))->every(2); //ListCollection([2,4])

(new ListCollection([1,2,3,4]))->groups(2);ListCollection([

ListCollection([1,2]),ListCollection([3,4]),

])

Few rules to make your code functional

Do not use

:) reassignments:) if:) null:) for:) foreach

Parallelism vs Concurrency

Future

public function nonBlockingGet(string $id): Future{ ...}

public function exampleAction(string $id1) : Response{ return $this ->nonBlockingService ->nonBlockingGet($id) ->map(function (NonBlockingGetResult $output) { return new Response($output); }) ->await();}

public function nonBlockingGet(string $id): Future{ ...}

public function exampleAction(string $id1) : Response{ return $this ->nonBlockingService ->nonBlockingGet($id) ->map(function (NonBlockingGetResult $output) { return new Response($output); }) ->await();}

Future<NonBlockingGetResult>

NYI

public function nonBlockingGet(string $id): Future{ ...}

public function exampleAction(string $id1) : Response{ return $this ->nonBlockingService ->nonBlockingGet($id) ->map(function (NonBlockingGetResult $output) { return new Response($output); }) ->await();}

Future<NonBlockingGetResult>

NYI

Future<Response>

public function nonBlockingGet(string $id): Future{ ...}

public function exampleAction(string $id1) : Response{ return $this ->nonBlockingService ->nonBlockingGet($id) ->map(function (NonBlockingGetResult $output) { return new Response($output); }) ->await();}

Future<NonBlockingGetResult>

NYI

Future<Response>

Response

public function nonBlockingGet(string $id): Future{ ...}

public function exampleAction(string $id1, string $id2, string $id3) : Response{ return Future::all([ $this->nonBlockingService1->nonBlockingGet($id1), $this->nonBlockingService2->nonBlockingGet($id2), $this->nonBlockingService3->nonBlockingGet($id3), ]) ->map(function ($output) { return new Response($output); }) ->await();}

NYI

Future & Parallel collections

use PhpSlang\Collection\ListCollection;...public function beautifulMultiplyBy(array $input, float $multiplication) : ListCollection{ return (new ListCollection($input)) ->map(function ($number) use ($multiplication) { return $number * $multiplication; });}

use PhpSlang\Collection\ParallelListCollection;...public function beautifulMultiplyBy(array $input, float $multiplication) : ParallelListCollection{ return (new ParallelListCollection($input)) ->map(function ($number) use ($multiplication) { return $number * $multiplication; });}

use PhpSlang\Collection\ListCollection;...public function asyncChainedComputationExample(array $input) : ListCollection{ return (new ListCollection($input))

->map($this->transformationOne());}

use PhpSlang\Collection\ParallelListCollection;...public function asyncChainedComputationExample(array $input) : ParallelListCollection{ return (new ParallelListCollection($input))

->map($this->transformationOne());}

use PhpSlang\Collection\ListCollection;...public function asyncChainedComputationExample(array $input) : ListCollection{ return (new ListCollection($input))

->map($this->transformationOne());}

use PhpSlang\Collection\ParallelListCollection;...public function asyncChainedComputationExample(array $input) : ParallelListCollection{ return (new ParallelListCollection($input))

->map($this->transformationOne());}

use PhpSlang\Collection\ParallelListCollection;...public function asyncChainedComputationExample(array $input) : ParallelListCollection{ return (new ParallelListCollection($input))

->map($this->transformationOne());}

CPU vs IO

use PhpSlang\Collection\ParallelListCollection;...public function asyncChainedComputationExample(array $input) : ParallelListCollection{ return (new ParallelListCollection($input))

->map($this->transformationOne())

->map($this->transformationTwo())

->map($this->transformationThree();}

use PhpSlang\Collection\ParallelListCollection;...public function asyncChainedComputationExample(array $input) : ParallelListCollection{ return (new ParallelListCollection($input)) ->map(function ($number) { return new Some($number) ->map($this->transformationOne())

->map($this->transformationTwo())

->map($this->transformationThree()

->get(); });}

use PhpSlang\Collection\ParallelListCollection;…public function asyncChainedComputationExample(array $input) : ParallelListCollection{ return (new ParallelListCollection($input)) ->map(function ($number) { return new Some($number) ->map($this->transformationOne())

->map($this->transformationTwo())

->map($this->transformationThree()

->get(); });}

What do I miss in PHP7 that Scala luckily has?

:/ Immutability by default:) Objects cloning:) Options:) Either:/ Future:) Parallel collections❏ Tail recurrency

:( Generic types:( Arrow functions:)/:( Pattern matching / case classes

Tail recurrency

def fibonacci(index : Int) : Int =index match { case 0 | 1 => index case _ => fibonacci(index - 1 ) + fibonacci(index - 2)}

function fibonacci(int $index) : int{ return in_array($index, [0, 1]) ? $index : fibonacci($index - 1) + fibonacci($index - 2);}

def fibonacci(index : Int) : Int =index match { case 0 | 1 => index case _ => fibonacci(index - 1 ) + fibonacci(index - 2)}

function fibonacci(int $index) : int{ return in_array($index, [0, 1]) ? $index : fibonacci($index - 1) + fibonacci($index - 2);}

echo fibonacci(123123123123);

Fatal error: Maximum function nesting level of '...' reached, aborting!

ini_set('xdebug.max_nesting_level', 9999999);

?

def fibonacci(index: Int): Int = { var a = 0 var b = 1 var i = 0

while (i < index) { val c = a + b a = b b = c i = i + 1 } return a}

function fibonacci(int $index) : int{ $a = 0; $b = 1; $i = 0;

while ($i < $index) { $c = $a + $b; $a = $b; $b = $c; $i += 1; } return $a;}

def recursiveFibonacci(n: Int, a:Int, b:Int): Int =n match { case 0 => a case _ => recursiveFibonacci( n-1, b, a+b ) }

def fibonacci( n : Int) : Int = recursiveFibonacci( n, 0, 1)

function recursiveFibonacci(int $n, int $a, int $b) { return ($n == 0) ? $a : recursiveFibonacci($n - 1, $b, $a + $b);}

function fibonacci(int $n) : int{ return recursiveFibonacci($n, 0, 1);}

def fibonacci(index : Int) : Int =index match { case 0 | 1 => index case _ => fibonacci(index - 1 ) + fibonacci(index - 2)}

function fibonacci(int $index) : int{ return in_array($index, [0, 1]) ? $index : fibonacci($index - 1) + fibonacci($index - 2);}

@tailrecdef recursiveFibonacci(n: Int, a:Int, b:Int): Int =n match { case 0 => a case _ => recursiveFibonacci( n-1, b, a+b ) }

def fibonacci( n : Int) : Int = recursiveFibonacci( n, 0, 1)

function recursiveFibonacci(int $n, int $a, int $b) { return ($n == 0) ? $a : recursiveFibonacci($n - 1, $b, $a + $b);}

function fibonacci(int $n) : int{ return recursiveFibonacci($n, 0, 1);} :(:)

Tail recurrencyTrampolines

Recurrency

Recurrency Trampoline

function recursiveFibonacci(int $n, int $a, int $b) { return ($n == 0) ? $a : recursiveFibonacci($n - 1, $b, $a + $b);}

function fibonacci($n){ return recursiveFibonacci($n, 0, 1);}

function recursiveFibonacci(int $n, int $a, int $b) { return ($n == 0) ? new Done($a) : new Bounce(function () use ($n, $b, $a) { return recursiveFibonacci($n - 1, $b, $a + $b); });}

function fibonacci($n){ return (new Trampoline(function () use ($n) { return recursiveFibonacci($n, 0, 1); }))->run();}

Recurrency Trampoline

What do I miss in PHP7 that Scala luckily has?

:/ Immutability by default:) Objects cloning:) Options:) Either:/ Future:) Parallel collections:) Tail recurrency

:( Generic types:( Arrow functions:)/:( Pattern matching / case classes

Conclusions

● Don’t be afraid of monads● Care about transparency (especially referential)

● Learn Haskell, Clojure, Scala, F#, JavaScript -> TypeScript

Witek Adamuswitold.adamus@xsolve.pl

http://phpslang.io