Save 37% off PRO during our Black Friday Sale! »

Introduction à la programmation fonctionnelle en PHP - Meetup PHP Bdx 2015

Introduction à la programmation fonctionnelle en PHP - Meetup PHP Bdx 2015

La « programmation fonctionnelle » est un terme qui revient de plus en plus fréquemment dans notre industrie. Cependant, beaucoup de développeurs trouvent ce paradigme particulièrement cryptique. C’est la raison pour laquelle je vous proposerai le 24 juin une introduction aux notions utilisées dans la programmation fonctionnelle.Après avoir survolé les fondements théoriques, nous verrons ce que recouvre la programmation fonctionnelle, et comment elle peut être utilisée au sein de PHP pour améliorer la qualité de vos développements.Même si des cas d’usages liés à PHP seront présentés, les notions liées à la programmation fonctionnelle sont universelles, les autres développeurs sont donc bienvenus.

Beb422437c1dfb5366f197919e41ac50?s=128

Arnaud LEMAIRE
PRO

June 24, 2015
Tweet

Transcript

  1. Programmation fonctionnelle & php

  2. SOLID à l’extrême !

  3. - Arnaud, Bordeaux le 24 juin 2015 « C’est super

    facile »
  4. Fonctions Définition function add_one($x) {
 return $x + 1;
 }


    
 add_one(2); //return 3

  5. Fonctions anonyme (lambda) Définition $add_one = function($x) {
 return $x

    + 1;
 };
 
 $add_one(2); //return 3
  6. Fonctions d’ordre supérieur Définition function add_one() {
 return function($x) {


    return $x + 1;
 };
 }
 
 $add_one = add_one();
 $add_one(2); //return 3
  7. Généralisation function add($a, $b) {
 return $a + $b;
 }


    
 add(2, 3); //return 5 Application partielle
  8. Application partielle Généralisation function add_one($x) {
 return add(1, $x);
 }


    
 add_one(2); //return 3
  9. Fonction closure Généralisation function add($a) {
 return function ($x) use

    ($a) {
 return $a + $x
 };
 }
 
 $add_one = add(1);
 $add_two = add(2);
 
 $add_one(2); //return 3
 $add_two(2); //return 4
  10. Définition C’est assez vague…

  11. - Wikipedia « La programmation fonctionnelle est un paradigme de

    programmation qui considère le calcul en tant qu'évaluation de fonctions mathématiques. »
  12. - Wikipedia « Comme le changement d'état et la mutation

    des données ne peuvent pas être représentés par des évaluations de fonctions la programmation fonctionnelle ne les admet pas, au contraire elle met en avant l'application des fonctions, contrairement au modèle de programmation impérative qui met en avant les changements d’état »
  13. Qu’est ce qu’un état ? Bonne question…

  14. - Développeur X « La programmation fonctionnelle c’est trop mainstream

    ! »
  15. Des éléments communs • Lambda & Closure • High order

    function
  16. Des éléments communs, que ne possède pas PHP • High

    order function • Tail call • Passage par référence ou par valeur • Pattern matching • Types algébriques
  17. Types algébriques • type Rank = 1|2|…|valet|dame|roi • type Suit

    = Club | Diamond | Spade | Heart • type Card = Suit * Rank • type Hand = Card list
  18. Machine de Turing vs Programmation Fonctionnelle Historique

  19. Définition : conclusion • La programmation fonctionnelle est une question

    de culture • Il s’agit d’une façon différente d’aborder la résolution de problème
  20. OOP Procedural programming Functional programming Class oriented programming Value oriented

    programming Function programming
  21. Quelques concepts important

  22. • Quelles différences entre une variable et une fonction ?

    • Une variable représente une donnée disponible immédiatement • Une fonction représente de la donnée future
  23. • Qu’est ce que l’immutabilité ? • Toute fonction (et

    méthode) ne doit jamais modifier de la donnée
 (mais peut retourner une copie modifiée)
  24. • Qu’est ce qu’un effet de bord ? • Quand

    une fonction modifie un élément qui lui est externe
  25. • Qu’est ce qu’un objet ? • Une collection de

    fonctions qui se partagent un pointeur commun (this)
  26. Définition La suite, le retour • Structure immutable • Sans

    effet de bord • Fonctions et donnée sont considérés à équivalence
  27. Les outils de la programmation fonctionnelle (en PHP)

  28. Closure - Injection de dépendance Closure (et non lambda) function

    find($filter) {
 return function($data_store) use ($filter) {
 return $data_store->find_by_criteria($filter);
 };
 }
 
 $criteria = find(['name' => 'Arnaud']);
 $users = $criteria(new UserRepository);
  29. Closure - Parametrization closure function get_unpaid_by_userId($user_id) {
 return function($order_by) use

    ($user_id) {
 return 
 "SELECT FROM invoices WHERE user_id = $user_id ORDER BY $order_by";
 };
 }
 
 $unordered_invoice = get_unpaid_by_userId(1);
 
 $ordered_by_date = $unordered_invoice('issue_date');
 $ordered_by_amount = $unordered_invoice(‘amount');
  30. Composition function unpaid_invoices($invoices) {
 return array_filter($invoices,
 function ($item) {
 return

    $item->isPaid();
 });
 }
 function invoices_amount($invoices)
 {
 return array_reduce($invoices,
 function ($carry, $item) {
 return $carry + $item->getAmount();
 }, 0);
 }
 function sum_of_unpaid_invoices($invoice) {
 return invoices_amount(
 unpaid_invoices($invoice)
 );
 };
  31. Fonction d’ordre supérieur High order function function apply_discount($tickets, $discount) {


    array_map(function($ticket) use ($discount) {
 $ticket->setPrice(
 $discount($ticket->getPrice()));
 }, $tickets);
 }
  32. Application partielle function add($a) {
 return function ($x) use ($a)

    {
 return $a + $x
 };
 }
 
 $add_one = add(1);
 $add_two = add(2);
 
 $add_one(2); //return 3
 $add_two(2); //return 4
  33. Avantages ? Ça sert à quoi finalement ?

  34. Mémoïzation function long_process($x) {
 static $results;
 
 if(is_null($results))
 $results =

    compute($x);
 
 return $results;
 }
  35. Lazyness function heavy_process_a($x) {
 return compute($x);
 }
 
 function heavy_process($x)

    {
 return function() use ($x) {
 compute($x);
 };
 }
  36. Parallélisation function parallelize($a) {
 process(range(0, $a/2));
 process(range($a/2, $a));
 }

  37. Tests & Robustesse Les tests deviennent triviaux assert(add_one(2), 3);

  38. Refactoring function very_complex_method($a) {
 //very complex process
 //another very complex

    process
 }
 
 function very_complex_process($a) {
 //very complex process
 }
 function another_very_complex_process($a) {
 //another very complex process 
 }
 function very_complex_method($a) {
 another_very_complex_process(
 very_complex_process($a));
 }
  39. Parametrisation à l’extrème

  40. function sum($to) {
 $sum = 0;
 for ($i = 0;

    $i<=$to; $i++) {
 $sum += $i;
 }
 return $sum;
 }
 
 function product($to) {
 $sum = 1;
 for ($i = 0; $i<=$to; $i++) {
 $sum *= $i;
 }
 return $sum;
 }
  41. $sum = function ($previous, $add) {
 return $previous + $add;


    };
 
 $product = function ($previous, $add) {
 return $previous * $add;
 }; function compute($to, $init, $action) {
 array_reduce(range(0, $to), $action, $init);
 }
 
 $sum_to = function($x) {
 return compute($x, 0, function($p, $a) {
 return $p + $a;
 });
 };
 $sum_to(3)
  42. function apply_discount($tickets) {
 foreach($tickets as $ticket) {
 $price = $ticket->getPrice();


    $ticket->setPrice($price-(0.20*$price));
 }
 
 return $tickets;
 }
  43. function apply_discount($tickets, $is_vip) {
 foreach($tickets as $ticket) {
 $price =

    $ticket->getPrice();
 if($is_vip)
 $ticket->setPrice($price-(0.33*$price));
 else
 $ticket->setPrice($price-(0.20*$price));
 }
 
 return $tickets;
 }
  44. function apply_discount($tickets, $client) {
 foreach($tickets as $ticket) {
 $price =

    $ticket->getPrice();
 if($client->isVip())
 $ticket->setPrice($price-(0.33*$price));
 else
 $ticket->setPrice($price-(0.20*$price));
 
 if($client->isFrequentBuyer()) {
 $price = $ticket->getPrice();
 $ticket->setPrice($price-5);
 }
 }
 
 return $tickets;
 }
  45. function apply_discount($tickets, $client) {
 if($client->isVip()) 
 $tickets = apply_vip_discount($tickets);
 else

    
 $tickets = apply_default_discount($tickets);
 
 if($client->isFrequentBuyer()) 
 $tickets = apply_frequentBuyer_discount($tickets);
 
 return $tickets;
 }

  46. function apply_discount($tickets, $action) {
 foreach($tickets as $ticket) {
 $price =

    $ticket->getPrice();
 $ticket->setPrice($action($price));
 }
 return $tickets;
 }
  47. function apply_discount($tickets, $action) {
 array_map(function($ticket) use ($action) {
 $ticket->setPrice($action($ticket->getPrice()));
 },

    $tickets);
 }
  48. function discount($ticket, $user) {
 $percentage_discount = function($x, $percentage) {
 return

    $x - ($percentage/100*$x);
 }; 
 $amount_discount = function($x, $amount) {
 return $x - $amount;
 }; 
 $vip_discount = function($x) use ($percentage_discount) {
 return $percentage_discount($x, 33);
 }; 
 $default_discount = function($x) use ($percentage_discount) {
 return $percentage_discount($x, 20);
 }; 
 $frequent_buyer_discount = function($x, $buyer_type_discount) use ($amount_discount) {
 return $amount_discount($buyer_type_discount($x), 5);
 }; 
 $frequent_buyer_discount_composable = function($buyer_type_discount) use ($frequent_buyer_discount) {
 return function($x) use ($buyer_type_discount, $frequent_buyer_discount) {
 return $frequent_buyer_discount($x, $buyer_type_discount);
 };
 }; 
 $applied_discount = ($user->isVip()) ? $vip_discount : $default_discount;
 
 return apply_discount($ticket, $frequent_buyer_discount_composable($applied_discount));
 }
  49. function compose() {
 $functions_list = array_reverse(func_get_args());
 $composed = function() use

    ($functions_list) {
 $first_function = array_shift($functions_list);
 return array_reduce(
 $functions_list,
 function ($carry, $item) {
 return $item($carry);
 },
 call_user_func_array($first_function, func_get_args())
 );
 };
 
 return $composed;
 }

  50. function available_discount_for($user) {
 $percentage_discount = function($x, $percentage) {
 return $x

    - ($percentage/100*$x);
 };
 $amount_discount = function($x, $amount) {
 return $x - $amount;
 };
 $vip_discount = function($x) use ($percentage_discount) {
 return $percentage_discount($x, 33);
 };
 $default_discount = function($x) use ($percentage_discount) {
 return $percentage_discount($x, 20);
 };
 $frequent_buyer_discount = function($x, $buyer_type_discount) use ($amount_discount) {
 return $amount_discount($buyer_type_discount($x), 5);
 };
 
 return compose($frequent_buyer_discount, 
 ($user->isVip()) ? $vip_discount : $default_discount);
 }
  51. function discount($ticket, $buyer) {
 return apply_discount($ticket, available_discount_for($buyer));
 };

  52. Transducers are coming

  53. function unpaid_invoices($invoices) {
 return array_filter($invoices,
 function ($item) {
 return $item->isPaid();


    });
 }
 function invoices_amount($invoices)
 {
 return array_reduce($invoices,
 function ($carry, $item) {
 return $carry + $item->getAmount();
 }, 0);
 }
 function sum_of_unpaid_invoices($invoice) {
 return invoices_amount(
 unpaid_invoices($invoice)
 );
 };
  54. $is_even = function($item) {
 if($item % 2 == 0) return

    true;
 return false;
 };
 
 $square = function($item) {
 return pow($item, 2);
 }; 
 array_map([1, 2, 3], $square);
 array_filter([1, 2, 3], $is_even);
  55. function map_reducer($callable, $iterable) {
 
 $map_reducer = function ($carry, $item)

    use ($callable){
 $carry[] = $callable($item);
 return $carry;
 };
 
 return array_reduce($iterable, $map_reducer, []);
 }
  56. function filter_reducer($callable, $iterable) {
 
 $filter_reducer = function ($carry, $item)

    use ($callable) {
 if($callable($item))
 $carry[] = $item;
 return $carry;
 };
 
 return array_reduce($iterable, $filter_reducer, []);
 }
  57. function make_mapper($callable) {
 return function($carry, $item) use ($callable){
 $carry[] =

    $callable($item);
 return $carry;
 };
 }
 
 array_reduce([1,2,3], make_mapper($square), []);
  58. function make_reducer($callable) {
 return function ($carry, $item) use ($callable) {


    if($callable($item))
 $carry[] = $item;
 return $carry;
 };
 }
 
 array_reduce([1,2,3], make_reducer($is_even), []);
  59. $appender = function ($array, $item) {
 $array[] = $item;
 return

    $array;
 };
  60. function mapping($map_callable) {
 $map_transducer = function($reducer) use ($map_callable) {
 $map_reducer

    = function($carry, $item) use ($reducer, $map_callable) {
 return $reducer($carry, $map_callable($item));
 };
 
 return $map_reducer;
 };
 
 return $map_transducer;
 }
  61. function filtering($filter_callable) {
 $filter_transducer = function($reducer) use ($filter_callable) {
 $filter_reducer

    = function($carry, $item) use ($reducer, $filter_callable) {
 if($filter_callable($item)) {
 return $reducer($carry, $item);
 } else {
 return $carry;
 }
 };
 
 return $filter_reducer;
 };
 
 return $filter_transducer;
 }
  62. $is_even_filter = filtering($is_even);
 $square_map = mapping($square);
 
 $transducers = $square_map($is_even_filter($appender));


    
 array_reduce([1,2,3,4], $transducers, []);
  63. $composition = compose(mapping($square), filtering($is_even));
 $composition_with_appender = $composition($appender);
 
 array_reduce([1,2,3,4], $composition_with_appender(),

    []);
  64. $composition = compose(
 mapping($invoices_amount),
 filtering($invoice_last_year),
 filtering($invoice_unpaid)
 )(); 
 $composition_with_appender =

    $composition($appender);
 
 array_reduce([1,2,3,4], $composition_with_appender(), []);
  65. The end et les monads ?