Functional PHP

by Larry Garfield

Tuesday 21 August 2012

@Crell

What we do

Warning, hard core geek action

Please pardon the nerd

But first, a little history...

Von Neumann Architecture

Von Neumann Architecture

Computer system bus

Von Neumann Architecture

Imperative Programming

This is what all modern hardware does

Imperative Programming is following a recipe for a cake

Procedural Programming

    Var MyList [5, 7, 2, 9, 2]
    Var Biggest
    Procedure FindBiggest
      For MyList As Item
        If Item > Biggest Then
          Biggest = Item
        End If
      End For
      Return
    End Procedure

    Call FindBiggest
    Print Biggest
    

Imperative Programming is following a recipe for a cake

Procedural Programming is singing a song with a refrain

Imperative Programming defines
how a program should work.

Declarative Programming

Declarative Programming defines
what a program should accomplish.

John Backus

"Can Programming Be Liberated From the von Neumann Style? A Functional Style and its Algebra of Programs"

Functional Programming

Algorithm-first

Functional Programming

Declare your algorithm

The compiler optimizes for you

"Functions" in the mathematical sense

Functional Programming

Pure functions

      function theme_list($items, $type = 'ul') {
        $output = '';
        foreach ($items as $item) {
          $output .= '
  • ' . $item . '
  • '; } return "<{$type}>" . $output . '<' . '/' . $type . '>'; }

    Benefits

    First-class functions

          $function = function($value) {
            return $value * 5;
          };
          $function($a);
        

    Benefits

    Immutable variables

    Benefits

    Recursion

          function factorial($a) {
            if ($a == 1) {
              return 1;
            }
            return $a * factorial($a - 1);
          }
        

    Benefits

    Put it all together

    The Fibonacci series

          function fibonacci($a) {
            if ($a == 0) {
              return 0;
            }
            if ($a == 1) {
              return 1;
            }
            return fibonacci($a - 1) + fibonacci($a - 2);
          }
    
          $fib = fibonacci(5);
        

    Imperative Programming is following a recipe for a cake

    Procedural Programming is singing a song with a refrain

    Functional Programming is a spreadsheet with formulas

    Strictly functional languages

    Functional PHP

    PHP lets you do all of that

    It just doesn't enforce it

    You can. You have the technology...

    <troll>PHP is as functional as LISP</troll>

    Anonymous functions

    $find_pair = function ($card1, $card2) {
      return ($card1->value == $card2->value);
    };
    
    $is_pair = $find_pair(new Card('2H'), new Card('8D'));
      
    ==
    class FindPair {
      public function __invoke($card1, $card2) {
        return ($card1->value == $card2->value);
      }
    }
    
    $find_pair = new FindPair();
    $is_pair = $find_pair(new Card('2H'), new Card('8D'));
        

    Closures

    $wild = new Card('5H');
    $find_pair = function ($card1, $card2) use ($wild) {
      return ($card1->value == $card2->value
              || $wild->value == $card1->value && $wild->suit == $card1->suit
              || $wild->value == $card2->value && $wild->suit == $card2->suit);
    };
    
    $is_pair = $find_pair(new Card('2H'), new Card('8D'));
      
    ==
    $wild = new Card('5H');
    class FindPair {
      protected $wild;
    
      public function __construct($wild) {
        $this->wild = $wild;
      }
    
      public function __invoke($card1, $card2) {
      return ($card1->value == $card2->value
              || $this->wild->value == $card1->value && $this->wild->suit == $card1->suit
              || $this->wild->value == $card2->value && $this->wild->suit == $card2->suit);
      }
    }
    
    $find_pair = new FindPair($wild);
    $is_pair = $find_pair(new Card('2H'), new Card('8D'));
        

    First-class functions in action

    $wild = new Card('5H');
    
    $is_wild = function($card) use ($wild) {
      return $wild->value == $card->value && $wild->suit == $card->suit
    }
    
    $find_pair = function ($card1, $card2) use ($is_wild) {
      return ($card1->value == $card2->value
              || $is_wild($card1)
              || $is_wild($card2));
    };
    
    $is_pair = $find_pair(new Card('2H'), new Card('8D'));
      

    Delayed execution

    
    class Container {
      protected $factories = array();
    
      public function __set($key, $func) {
        $this->factories[$key] = $func;
      }
    
      public function __get($key) {
        return $this->factories[$key]($this);
      }
    }
      
    $container = new Container();
    $container->conn = function($c) {
      return new DatabaseConnection($c->conn_info);
    };
    $container->conn_info = function() { return array('user' => 'me', 'pass' => 'hi'); };
    
    $conn = $container->conn;
    $conn->query('...');
    
        

    Hey, look, a Dependency Injection Container!

    See also: Pimple

    Lexical scoping

    Lame...

    function double($a) {
      return $a * 2;
    }
    function something_important(array $arr) {
      $arr = array_map('double', $arr);
      // ...
    }
      

    Worse...

    function multiply($a) {
      return $a * $GLOBALS['factor'];
    }
    function something_important(array $arr, $factor) {
      $GLOBALS['factor'] = $factor;
      $arr = array_map('multiply', $arr);
      // ...
    }
        

    Slick...

    function something_important(array $arr, $factor) {
      $arr = array_map(function ($a) use ($factor) {
        return $a * $factor;
      }, $arr);
      // ...
    }
        
    Especially useful for preg_replace_callback()

    Drupal example

      public function onKernelControllerLegacy(FilterControllerEvent $event) {
        $request = $event->getRequest();
        $router_item = $request->attributes->get('drupal_menu_item');
        $controller = $event->getController();
    
        // This BC logic applies only to functions. Otherwise, skip it.
        if (is_string($controller) && function_exists($controller)) {
          $new_controller = function() use ($router_item) {
            return call_user_func_array(
              $router_item['page_callback'],
              $router_item['page_arguments']);
          };
          $event->setController($new_controller);
        }
      }
      

    Currying

    Haskell Curry

    Haskell B. Curry

    1900 - 1982

    Yet another crazy smart mathematician

    Currying

    Splitting a multi-parameter function
    into multiple single-parameter functions

    A special case of partial-application

    Partial-application

    $add = function ($a, $b) {
      return $a + $b;
    };
      
    $add = function ($a) {
      return function($b) use ($a) {
        return $a + $b;
      };
    };
    
    $add1 = $add(1);
    $add5 = $add(5);
    
    $x = $add5($y);
        
    $add = function ($a) {
      return function($b) use ($a) {
        return $a + $b;
      };
    };
    
    $adder = $add(variable_get('increment_amount'));
    $x = $adder($y);
        

    Partial-application

    $add = function ($a) {
      return function($b) use ($a) {
        return $a + $b;
      };
    };
    
    $adder = $add(variable_get('increment_amount'));
    
    $values = array_map($adder, $array);
      

    Memoization

    Memoization

    function expensive($key) {
      static $keys = array();
    
      if (empty($keys[$key])) {
        $keys[$key] = /* some expensive operation */
      }
    
      return $keys[$key];
    }
      

    Memoization

    $factorial = function($a) use (&$factorial)  {
      print "Called function with $a" . PHP_EOL;
      if ($a == 1) {
        return 1;
      }
      return $a * $factorial($a - 1);
    };
    
    print "Result: " . $factorial(3) . PHP_EOL;
    print "Result: " . $factorial(4) . PHP_EOL;
      
    Called function with 3
    Called function with 2
    Called function with 1
    Result: 6
    Called function with 4
    Called function with 3
    Called function with 2
    Called function with 1
    Result: 24
        

    Memoization

    function memoize($function) {
      return function() use ($function) {
        static $results = array();
        $args = func_get_args();
        $key = serialize($args);
        if (empty($results[$key])) {
          $results[$key] = call_user_func_array($function, $args);
        }
        return $results[$key];
      };
    }
    
    $factorial = memoize($factorial);
    
    print "Result: " . $factorial(3) . PHP_EOL;
    print "Result: " . $factorial(4) . PHP_EOL;
      
    Called function with 3
    Called function with 2
    Called function with 1
    Result: 6
    Called function with 4
    Result: 24
        

    Pure functions are awesome!

    You must separate your logic from your I/O!

    You should be doing that anyway...

    The Functional Way(tm)

    Map/Reduce

    Map/Reduce

    Fold

    Filter

    The take aways...

    Functional programming is not a language

    It is a methodology

    It is a state of mind

    What did you think?

    Locate this session on the DrupalCon Munich website

    http://munich2012.drupal.org/program/sessions/functional-php

    Thank you!