Why async and functional programming in PHP7 suck and how to get overr it?
-
Upload
witek-adamus -
Category
Engineering
-
view
53 -
download
2
Transcript of Why async and functional programming in PHP7 suck and how to get overr it?
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' [email protected],❏ Rasmus Schultz [email protected]
❏ 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 [email protected]
http://phpslang.io