DrupalCon review: Functional programming
Arrived in Munich
Reading through the curriculum of the Drupalcon I was surprised to find a session of Larry Garfield to the topic functional PHP. Functional programming has as much to do with PHP and Drupal as Cocomore with boredom, so at least my view. How should a functional approach, with all its radiating abstraction and elegance, be integrated in a system that is built up procedural thoroughly? The two worlds seemed different to me: on the one hand Drupal, a classical modular and database-based framework, on the other hand the functional programming with the guiding principle of statelessness. With interest I expected the presentation.
The basics
According to preliminary information about his person, Larry opened his session with a comparison of the current paradigms of programming. The main difference: Imperative programming defines and changes conditions in a sequential order. Each step will be entered by the developer, as he would write a cake recipe. The developer decides, how something will be implemented. In contrast, functional programming (FP) orients on the finish: the developer decides what should be implemented. The process is non sequential and single steps are ideally independent and parallelizable.
- pure functions, i.e. stateless, deterministic, side effect free functions
- higher-order functions and first-class functions, i.e. nesting of functions
- invariant variables (!), functions that return a literal
- recursion
He especially pointed to the lack of state: Due to the fact that functions in all circumstances always provide same result, source code can not only be better tested, but even verified.
Functional programming in PHP
<troll>
PHP is as functional as LISP</troll>
Anonymous function
<?php $find_pair = function ($card1, $card2) { return ($card1->value == $card2->value); }; $is_pair = $find_pair(new Card('2H'), new Card('8D'));
Closure
<?php $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'));
<?php function something_important(array $arr, $factor) { $arr = array_map(function ($a) use ($factor) { return $a * $factor; }, $arr); // ... }
Currying
<?php $add = function ($a, $b) { return $a + $b; };
... could also be nested transferred into two functions.
<?php $add = function ($a) { return function($b) use ($a) { return $a + $b; }; }; $add1 = $add(1); $add5 = $add(5); $x = $add5($y);
Memoization
<?php 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;
Reality check
Inspired by the lecture, I was trying to implement the gained knowledge in a project which used a parameterized integral function (fα,β(x), α, β, x n-dimensional each) to calculate an index.
<?php function myfunction($alpha, $beta, $x) { }
Since the parameters remained arbitrary but fixed at run time, a partial evaluation was an opportunity, so that the function in the end depended on x.
<?php
function index($x) {
$myfunction = function ($alpha, $beta) use ($z) {
//...
};
$result1 = $myfunction($x[1]);
$result2 = $myfunction($x[2]);
//...
}
Conclusion
<?php 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); } }