Currying in PHP
Mithilfe vom Currying (benannt nach Haskell Brooks Curry) kann man eine n-stellige Funktion in n einstellige Funktionen umwandeln. Da PHP keine funktionale Programmiersprache ist, wird dieses Verfahren zwar nicht unterstützt, aber mithilfe der Closures aus PHP 5.3 lässt sich eine Funktion curry entwickeln, die das Currying-Verfahren auf PHP überträgt. Bevor ich zur Implementierung komme, möchte ich Currying anhand von Haskell beschreiben. Wir beginnen bei einem trivialen Beispiel: Der Multiplikation.
(*) :: (Num a) => a -> a -> a
(4*) :: (Num a) => a -> a
(4*2) :: (Num a) => a
An den Typen der Ausdrücke (*), (4*) und (4*2) kann man bereits den Einsatz von Currying erkennen, wenn man weiß, dass die Klammerung bei Typsignaturen rechtsassoziativ ist. Schreiben wir diese Klammern explizit hin, ist der Typ von (*) nämlich (a -> (a -> a)). Wenn man die Grundlagen funktionaler Programmierung beherrscht, kann man daraus ablesen, dass (*) ein Argument nimmt und eine Funktion vom Typ (a -> a) zurückgibt. Wenn man der zurückgegebenen Funktion ein weiteres Argument gibt, bekommt man eine null-stellige Funktion vom Typ a zurück. In Haskell geschieht dieses Currying und die damit verbundene partielle Anwendung von Funktionen transparent, sodass man diesem Umstand meist nicht gewahr ist.
Im Grunde genommen soll es also nur stets einstellige Funktionen geben, die entweder eine weitere einstellige Funktion oder das Ergebnis zurückgeben. Das Multiplikationsbeispiel könnte man in PHP folgendermaßen beschreiben:
$mult = function($x) {
return function ($y) use ($x) {
return $x*$y;
};
};
$f = $mult(4);
$g = $f(2);
$mult ist hierbei eine einstellige-Funktion, die eine weitere einstellige Funktion zurückliefert. $mult(4) = $f ist eine solche einstellige Funktion, die ihr Argument mit 4 multipliziert und das Ergebnis $f(2) = $g = 8 zurückliefert. Die Art und Weise, mit der die zwei-stellige Multiplikation in eine gecurryte Funktion umgewandeln wurde, kann man für beliebige n adaptieren, wie folgender Code zeigt:
function curry($f, array $args = array()) {
$refl = new ReflectionFunction($f);
$n = $refl->getNumberOfParameters();
if($n <= 1) {
return $f;
}
if(count($args) == $n-1) {
return function ($x) use ($f, $args) {
$args[] = $x;
return call_user_func_array($f, $args);
};
}
return function ($x) use ($f, $args) {
$args[] = $x;
return curry($f, $args);
};
}
Hierbei wird großer Nutzen von Closures und der Reflection-API gemacht. Insgesamt spiegelt der Algorithmus die Idee, wie ich sie bereits beschrieben habe, wieder. Die notwendigen Anpassungen, damit man die Idee in PHP ausdrücken kann, sind rein technischer Natur. Allerdings haben solche gecurryte Funktionen einen Nachteil: Man kann nicht mehrere Argumente zugleich auswerten. Denkbar wären folgende Varianten:
$mult(4)(2); // funktioniert nicht
$mult(4,2); // funktioniert auch nicht, wäre aber denkbar
// eigene:
function call($f) {
return array_reduce(
array_slice(func_get_args(), 1),
function ($f, $p) { return $f($p); },
$f
);
}
call($mult, 4, 2);
Während die erste Variante durch die PHP-Syntax nicht ermöglicht wird, wäre die zweite Variante eine denkbare Alternative, die allerdings einige Spezialfälle einführen würde. Eine eigene Funktion zur Auswertung mehrere Argumente ist jedoch auch sehr schnell implementiert und zugleich verständlich.
Da es sich bei diesen Funktionen um Ideen aus der funktionalen Programmierung handelt, habe ich einen eigenen Ordner PhP, der für Phunctional Programming steht, in einem Github-Repository eingeführt, wo sich im Laufe der Zeit weitere solche Funktionen ansammeln werden.
KingCrunch schreibt:
Schaus dir mal an:
http://gist.github.com/325482
Blackflash schreibt:
Das Problem an dieser Lösung ist, dass die Closure sofort ausgewertet wird, d.h. man muss alle erforderlichen Parameter übergeben, was bei "echtem" Currying nicht der Fall ist. Beispiel "Multiplikation":
$mult = curry('*'); // Du weißt ja, was gemeint ist. :-)
$double = $mult(2); // $double($x) verdoppelt den Wert von $x
$x = $double(21); // $x == 42