DrupalCon Review: Funktionale Programmierung

11.02.2015 Cocomore

Hört man den Begriff der funktionalen Programmierung, so denkt man unwillkürlich zurück an Vorlesungen, in denen akademische, zumeist mathematische Problemstellungen elaboriert worden sind. Nicht zu Unrecht: Funktionale Programmiersprachen eignen sich hervorragend, Algorithmen, die (komplexe) mathematische Funktionen enthalten, umzusetzen. Dass sie sich auch abseits der Universitäten zu ernsthaften Alternativen der etablierten Sprachen gewandelt haben, beweisen die Marktgiganten Facebook, das die funktionale Sprache Erlang für die Umsetzung seiner Chatfunktion verwendet, und Google mit dem MapReduce-Framework, welches ausgelegt ist, immens große Datenmengen (im Petabyte-Bereich) zu verarbeiten.

Angekommen in München

Gleichsam war ich überrascht, beim Durchlesen des Curriculums der Drupalcon eine Session von Larry Garfield zum Thema Functional PHP zu finden. Funktionale Programmierung hätte mit PHP und Drupal etwa so viel zu tun wie Cocomore mit Langeweile, so jedenfalls meine Auffassung. Wie sollte ein funktionaler Ansatz, bei aller Abstraktion, bei aller Eleganz, die er austrahlt, zu integrieren sein in ein System, das durch und durch prozedural aufgebaut ist? Zu unterschiedlich erschienen mir die zwei Welten: Auf der einen Seite Drupal, ein klassisches Framework, das modular und datenbankbasiert strukturiert ist, auf der anderen Seite die funktionale Programmierung mit dem Leitsatz der Zustandslosigkeit. Mit Interesse erwartete ich den Vortrag.

Die Grundlagen

Nach einleitenden Informationen zur Person, eröffnete Larry seine Session mit einem Vergleich der gängigen Paradigmen der Programmierung.  Der Hauptunterschied: Imperative Programmierung definiere und ändere Zustände in einer sequentiellen Abfolge. Jeder einzelne Schritt werde vom Entwickler eingegeben, als würde er ein Kuchenrezept schreiben. Der Entwickler entscheide also, wie etwas umgesetzt wird.  Demgegenüber orientiere sich die funktionale Programmierung (FP) am Ziel: Der Entwickler entscheide, was umgesetzt werden soll. Der Ablauf sei nichtsequentiell und die einzelnen Schritte im Idealfall unabhängig und parallelisierbar.

Auf die wissenschaftlichen Grundlagen, z.B. dem Lambda-Kalkül oder der Turing-Vollständigkeit ging Larry leider nur am Rande ein; dennoch ließ er es sich nicht nehmen, die wichtigsten Theoretiker, zumeist Mathematiker, auf diesem Gebiet ehrenhaft zu erwähnen, was mir sehr gefiel.

FP habe 4 wichtige Eigenschaften:

  • Reine Funktionen (Pure functions), d.h. zustandslose, deterministische, seiteneffektfreie Funktionen
  • Funktionen höhere Ordnung (Higher-order functions) / First-Class-Funktionen (first-class functions), d.h. Verschachtelung von Funktionen
  • Unveränderliche Variablen (!), das sind Funktionen, die ein Literal zurückliefern
  • Rekursion

Besonders wies er auf die Zustandslosigkeit hin: Dadurch, dass Funktionen unter allen Umständen immer dasselbe Ergebnis liefern, könne Quellcode nicht nur besser getestet, sondern sogar verifiziert werden.

Funktionale Programmierung in PHP

Den Übergang zu PHP formulierte Larry wie folgt:

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

PHP wäre so funktional wie LISP! Unruhe verbreitete sich. Jeder, der schon in LISP oder Haskell programmiert hatte und gleichzeitig PHP 5.2 kannte, wusste um die Verwegenheit dieser Aussage. Wie konnte man die Behauptung untermauern? Zunächst sei PHP 5.3 Mindestvoraussetzung. Diese Version liefere einen wichtigen Baustein für die FP, die so genannte anonyme Funktion, also Funktionen ohne eigenen Funktionsnamen, welche aus anderen Sprachen, z.B. Javascript bekannt sein sollte.

Anonyme Funktion

<?php
$find_pair = function ($card1, $card2) {
  return ($card1->value == $card2->value);
};

$is_pair = $find_pair(new Card('2H'), new Card('8D'));

Ein ähnliches Konstrukt seien Closures, welche sich nur durch einen zusätzlichen Parameter im Sichtbarkeitsbereich von anonymen Funktionen unterscheiden. Dadurch könnten von außen weitere Daten, bespielsweise ein Objekt, mit in die Funktion einfließen. Der Nachteil sei, dass man die Vorteile der reinen FP untergrabe.

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'));

Besonderen Nutzen erhalte man, wenn man anonyme Funktionen und Closures in Verbindung mit Funktionen nutzt, die als Argument eine Funktion erwarten, beispielsweise array_map, oder preg_replace_callback.

Anonyme Funktionen als Callback

<?php
function something_important(array $arr, $factor) {
  $arr = array_map(function ($a) use ($factor) {
    return $a * $factor;
  }, $arr);
  // ...
}

Benannt nach Haskell Curry, bezeichne man mit Currying das Aufteilen einer Funktion mit mehreren Parametern in mehrere Funktionen mit einem Parameter. Eine Additionsfunktion bespielsweise ...

Currying

<?php
$add = function ($a, $b) {
  return $a + $b;
};

... könne auch verschachtelt in zwei Funktionen überführt werden.

<?php
$add = function ($a) {
  return function($b) use ($a) {
    return $a + $b;
  };
};

$add1 = $add(1);
$add5 = $add(5);

$x = $add5($y);

Dadurch ergebe sich eine neue Technik der Wiederverwendbarkeit: Die partielle Auswertung. Die innere Funktion werde sowohl von $add1 als auch $add5 verwendet.

Die letzte Technik mit dem künstlichen Namen Memoization diene der Performance-Optimierung. Bereits berechnete Ergebnisse werden (hier in einer statischen Variable) zwischengespeichert, so dass bei erneutem Aufruf mit identischen Parametern die Funktion nicht erneut ausgewertet werden müsse.

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;

Realitätscheck

Das letzte Mal verwendete ich eine anonyme Funktion zu Drupal-6-Zeiten, um eine Sortierungsfunktion einzubinden.  Es dauerte nicht lange, bis eine aufgebrachte Menge an Frontend-Programmierern sich zu mir begab. Hatten sie die Eleganz der anonymen Funktion erkannt? Wohl kaum, denn auf ihrem Server lief eine PHP 5.2 Version. Das einzige, was sie zu sehen bekamen, war ein schlichter weißer Bildschirm.

Inspiriert vom Vortrag, versuchte ich, das erlangte Wissen umzusetzen, und zwar in einem Projekt, das eine parametrisierte Integralfunktion (fα,β(x) , α,β, x jeweils n-dimensional) zur Indexberechnung nutzte.

<?php
function myfunction($alpha, $beta, $x) {

}

Da die Parameter zwar beliebig, zur Laufzeit jedoch fest blieben, bot sich eine partielle Auswertung an, so dass die Funktion letzlich nur noch von x abhing.

<?php
function index($x) {
  $myfunction = function ($alpha, $beta) use ($z) {
  //...  
  };
  
  $result1 = $myfunction($x[1]);
  $result2 = $myfunction($x[2]);
  //...
}

Fazit

Funktionale Programmierung ist einsetzbar in PHP. Es hängt von der Aufgabenstellung ab, ob FP eingesetzt werden kann oder sollte. Wenn man FP einsetzt, sollte man sich im Klaren sein, dass eine zustandslose, seiteneffektfreie Programmierung nur schwer zu erreichen ist; Man denke an Closures: Strenggenommen wird hier von außen eine Information, also ein Zustand, induziert, was dem Prinzip der Zustandslosigkeit widerspricht. Popularität dürfte die Anwendung von anonymen Funktionen erreichen, aus Gründen der Übersichtlichkeit und Struktur. Eine komplexere Anwendung von FP ist bei baumähnlichen Datenstrukturen vorstellbar.

In Bezug auf Drupal wird der große Einzug von FP wohl ausbleiben. Punktuell gibt es aber auch hier Einsatzmöglichkeiten. Exemplarisch hat Larry folgende Stelle aus dem Drupal 8 Core genannt.


<?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);
  }
}

Abzuwarten ist, ob die neuen Funktionen von PHP 5.4 bezüglich anonymen Funktionen von der Community angenommen werden. Besonders objektorientierte Frameworks dürften von den Neuerungen (z.B. den Object extensions) profitieren.