Anonyme Funktionen in PHP

Ganz ohne Namen
Kommentare

Anonyme Funktionen gehören inzwischen in vielen Programmiersprachen zum regulären Funktionsumfang. Neben funktionalen Programmiersprachen, wie Scheme, Scala, Haskell, Clojure und auch JavaScript, setzen vermehrt auch die klassischen Sprachen wie Ada, Python, Ruby oder C# auf die Eleganz und Einfachheit von funktionalen Aspekten in der Sprache. Auch PHP ist mit der Version 5.3 auf diesen Zug aufgesprungen. Der folgende Artikel soll das Konzept der anonymen Funktionen in PHP näher beleuchten und Einsatzmöglichkeiten aufzeigen.

Im Zusammenhang mit anonymen Funktionen tauchen in der Literatur immer wieder auch die Begriffe Closure oder Lambda-Funktion auf. Im Anschluß „Anonyme Funktion, Closure oder Lambda-Funktion?“ werden die drei Begriffe theoretisch erläutert und voneinander abgegrenzt. Wie aber sieht das in der Realität aus? Hierzu sehen wir uns ein Beispiel aus JavaScript an – einfach deshalb, weil die meisten Entwickler, die schon einmal irgendetwas in JavaScript entwickelt haben, fast zwangsläufig über derartige Konstrukte, die anonyme Funktionen und Closures in Aktion zeigen, gestolpert sind:

var a = 1,
  f = function(b) {
    a += b;
    return a;
  };
console.log(f(2)); // 3
console.log(a);    // 3

Der vorige Code zeigt an einem einfachen Beispiel sehr gut das Grundkonzept hinter anonymen Funktionen und das Closure-Verhalten in JavaScript. Die Funktion f(b) hat keinen eigenen Namen – f ist lediglich die Variable, der die Funktion zugewiesen wurde, um überhaupt noch Zugriff auf die Funktion zu erhalten. Die Funktion f(b) lässt sich also nur aufrufen, wenn eine Referenz auf die Variable f zur Verfügung steht. Anonyme Funktionen, bzw. alle Funktionen in JavaScript, verhalten sich daher nicht anders als andere Typen. Man spricht hier auch davon, dass Funktionen „first class citizens“ sind. Den Closure-Aspekt kann man an der Variable a erkennen. Obwohl a nicht im Kontext von f(b) definiert wurde, kann aus f(b) auf a zugegriffen werden. Bei der Funktionserstellung wird also der Erstellungskontext gespeichert und steht zur Ausführungszeit von f(b) noch zur Verfügung (auch dann, wenn der Erstellungskontext gar nicht mehr existieren sollte, weil z. B. eine Funktion bereits wieder verlassen wurde). Die anonyme Funktion f(b) umschließt („to enclose“), speichert und erhält daher ihren Erstellungskontext. Dass im obigen Beispiel auch noch a aus f(b) heraus modifiziert werden kann, liegt daran, dass JavaScript grundsätzlich nur Referenztypen kennt.

In PHP können ab der Version 5.3 gleiche bzw. ähnliche Konstrukte erstellt werden und erlauben so in vielen Fällen elegantere und einfachere Lösungen als dies bisher mit PHP möglich war.

Anonyme Funktion, Closure oder Lambda-Funktion?

Anonyme Funktion: Eine anonyme Funktion […] ist eine Funktion in einem Computerprogramm, die nicht über ihren Namen, sondern nur über Verweise wie Referenzen oder Zeiger angesprochen werden kann. Lambda-Funktion: Der Name Lambda-Funktion wird üblicherweise als Synonym für anonyme Funktionen verwendet. Der Name entstammt dem Lambda-Kalkül. Closure: Als Closure oder Funktionsabschluss bezeichnet man eine Programmfunktion, die sich ihren Erstellungskontext „merkt“. Beim Aufruf kann die Funktion dann auf diesen zugreifen, selbst wenn der Kontext außerhalb der Funktion schon nicht mehr existiert. Eine Closure muss damit nicht zwangsläufig eine anonyme Funktion sein.

callback – unser Einstieg in anonyme Funktionen

PHP an sich kennt schon lange das Konzept von so genannten callback-Funktionen, also Funktionsreferenzen. In internen array-Funktionen wie zum Beispiel array_walk(), array_map(), usort(), aber auch bei der Registrierung von eigenen session Handlern über session_set_save_handler() oder beim XML-Parsen mit Low-Level-libxml-Funktionen über xml_set_*_handler() werden callback-Parameter gefordert. Vor PHP 5.3 gab es im Grunde vier Möglichkeiten diese Parameter zu setzen (Listing 1).

$a = array(10, 4, 8, 9, 2, 3, 7, 1, 6, 5);

// 1) reguläre Funktion als callback
function randomSort($a, $b) {
  return rand(-1, 1);
}
usort($a, 'randomSort');

// 2) Instanz-Methode als callback
class Sorter {
  public function sort($a, $b) {
    return $b - $a;
  }
}
$sorter = new Sorter();
usort($a, array($sorter, 'sort'));

// 3) (statische) Klassen-Methode als callback
class StaticSorter {
  public static function sort($a, $b) {
    return $a - $b;
  }
}
usort($a, array('StaticSorter', 'sort'));
// 3a) oder
usort($a, 'StaticSorter::sort');

// 4) anonyme Funktion prä-PHP-5.3-Ära
$oldSchoolAnonymousFunction = create_function('$a, $b', 'return $a - $b;');
usort($a, $oldSchoolAnonymousFunction);

// 5) ausführbare Objekt-Instanzen (PHP >= 5.3)
class InvokeSorter {
  public function __invoke($a, $b) {
    return $a - $b;
  }
}
$sorter = new InvokeSorter();
usort($a, $sorter);

Wenn man einmal von der sehr unschönen vierten Variante absieht (die im Grunde nichts anderes ist als eval()-Code), sind die vorgenannten callback-Optionen durchaus sinnvoll und einsetzbar. Oft jedoch wünscht man sich etwas mehr Flexibilität oder einfach weniger zu pflegenden Code für die Erledigung einfacher Aufgaben wie vielleicht das Sortieren eines Arrays.

Aufmacherbild: invisible men von Shutterstock / Urheberrecht: Savchenko

[ header = Closure – die anonyme Klasse ]

Closure – die anonyme Klasse

Beginnend mit PHP 5.3 stehen den Entwicklern nun auch anonyme Funktionen zur Verfügung, die den Namen auch verdient haben. Anfangs nur als Implementierungsdetails vorgesehen, werden anonyme Funktionen als Instanzen der Closure-Klasse abgebildet. Mit PHP 5.4 ist die Closure-Klasse aber fester Bestandteil der Sprache und erfüllt natürlich den callable Type-Hint (Kasten: „PHP 5.4“). Genug der Theorie, schauen wir uns in Listing 2 die neuen anonymen Funktionen an.

= 5.3">$sorter = function($a, $b) {
  return $a - $b;
};

print_r($sorter);
/*
Closure Object
(
  [parameter] => Array
  (
    [$a] => 
    [$b] => 
  )
)
*/

$a = array(10, 4, 8, 9, 2, 3, 7, 1, 6, 5);
usort($a, $sorter);

Die Deklaration entspricht dem, was man vermutlich von PHP erwartet hätte, und auf den ersten Blick gleicht die Erstellung einer anonymen Funktion der in JavaScript. Es gibt jedoch erhebliche Unterschiede, die wir uns ein wenig genauer anschauen wollen.

JavaScript vs. PHP

Der sicherlich entscheidendste Unterschied zwischen den Implementierungen in JavaScript und PHP ist die Closure-Eigenschaft, die in JavaScript inhärent ist. In JavaScript ist jede Funktion eine Closure und kann auf ihren Erstellungskontext zugreifen. PHP hingegen fordert den Entwickler auf, ein Closure-ähnliches Verhalten explizit zu definieren:

// JavaScript, Ausgabe in der Konsole: 2
var a = 1;
var f = function() {
  console.log(a);
};
a = 2;
f();

Am Beispiel sieht man, dass JavaScript beim Funktionsaufruf direkten Zugriff auf den Erstellungskontext (Variable a) hat. Interessant hierbei ist noch, dass der Zugriff über eine Referenz erfolgt, sodass Änderungen zwischen der Closure und dem Erstellungskontext ausgetauscht werden. Probieren wir dasselbe in PHP:

// PHP, das selbe Beispiel
// Ausgabe: PHP Notice: Undefined variable: a ...
$a = 1;
$f = function() {
  echo $a;
};
$a = 2;
$f();

Anonyme Funktionen in PHP sind also keine Closures – daher wird es auch etwas befremdlich, dass die Implementierung über eine Klasse Closure erfolgt.

Ein Closure-Verhalten aber erhöht die Nutzbarkeit von anonymen Funktionen deutlich und ist für einige Entwurfsmuster in diesem Zusammenhang sogar unerlässlich. PHP bietet daher dem Entwickler die Möglichkeit, mithilfe des use-Keywords explizit Variablen aus dem Erstellungskontext in die anonyme Funktion zu importieren und so eine Art Closure zu schaffen:

$a = 1;
$f = function() use ($a) {
  echo $a;
};
$a = 2;
$f();

use erlaubt die Übernahme von Variablen aus dem Erstellungskontext in die anonyme Funktion; immer noch aber ist die Ausgabe nicht das erwartete „2“ sondern lediglich „1“.

Wie im Beispiel schön zu sehen ist, erfolgt die Übernahme der Variable in den Funktionskontext aber nicht per Referenz, sondern, wie in PHP für einfache Typen üblich, per „Copy-on-Write“. $a ist daher mit der Erstellung von $f() auf den Wert „1“ festgeschrieben („call-by-value“). Auch dieses Verhalten ist oft noch nicht ausreichend, da der referenzielle Zugriff eine wichtige Eigenschaft von Closures ist. Wie bei regulären Funktionsargumenten auch, besteht nun auch bei anonymen Funktionen die Möglichkeit, die Übergabe als Referenz zu erzwingen („call-by-reference“):

$a = 1;
$f = function() use (&$a) {
  echo $a;
};
$a = 2;
$f();

use (&$a) erzwingt die Übergabe als Referenz; die Ausgabe lautet nun auch „2“. Das letzte Beispiel kommt dem einer echten Closure in PHP am nächsten, fordert vom Entwickler aber immer noch die explizite Übergabe von Kontextvariablen.

Leider hat PHP aber noch einige weitere Einschränkungen bei der Verwendung von anonymen Funktionen, die man aus JavaScript nicht kennt. Wir wollen diese Fallstricke kurz beleuchten. Anonyme Funktionen in JavaScript sind tatsächlich „first class citizens“, ihre Verwendung in der gesamten Sprache ist völlig transparent. So ist das Zuweisen und Ausführen von anonymen Funktionen zu Objekteigenschaften kein Problem:

// Anonyme Funktionen als Objekteigenschaften in JavaScript
var a = {
  b: function() {}
};
a.b();

PHP hat damit jedoch ein Problem:

// PHP
$a = new stdClass();
$a->b = function() {};
$a->b(); // PHP Fatal error:  Call to undefined method stdClass::b() ...
$f = $a->b;
$f();
// oder
call_user_func($a->b);

PHP erlaubt das Zuweisen von anonymen Funktionen zu Objekteigenschaften, das direkte Ausführen wird aber dadurch verhindert, dass PHP eine Klassenmethode stdClass::b() erwartet.

Ausführen lassen sich anonyme Funktionen in Objekteigenschaften also nur durch das vorherige Zuweisen zu einer lokalen temporären Variable oder über call_user_func() bzw. call_user_func_array(). Dasselbe gilt im Übrigen natürlich auch, wenn die anonymen Funktionen in Klassendefinitionen zugewiesen werden.

Nächstes Problem: Wie bereits erwähnt sind anonyme Funktionen eine absolut natürliche Spezies in JavaScript. Die Funktionen können daher auch mit eigenen Eigenschaftsvariablen versehen werden:

var f = function() {};
f.a = 1;
console.log(f.a);

In JavaScript sind anonyme Funktionen vollwertige Objekte. Die interne Closure-Implementierung in PHP ist demgegenüber eingeschränkt:

$f = function() {};
$f->a = 1;

Die Closure-Klasse in PHP reagiert hier allergisch: „Catchable fatal error: Closure object cannot have properties …“

Ein weiteres Problem, das im Zusammenhang mit dem PHP-Parser steht, ist das direkte Ausführen einer erstellten anonymen Funktion. Für JavaScript ist Folgendes kein Problem, denn JavaScript erlaubt das direkte Ausführen zurückgegebener anonymer Funktionen:

var f = function() { return function() { console.log('Hi'); } }
f()(); 

In PHP führt der ähnliche Code zu einem Syntaxfehler: „PHP Parse error: parse error …“ (PHP 5.3) oder „Parse error: syntax error, unexpected ‚(‚ …“ (PHP 5.4):

$f = function() { return function() { echo 'Hi'; }; };
$f()();

[header = Binden von anonymen Funktionen ]

Binden von anonymen Funktionen – was ist „$this“?

Bleibt noch ein markanter Unterschied – zumindest in PHP 5.3. Anonyme Funktionen in JavaScript sind „freie“ Funktionen, die sich bei der Ausführung an bestehende Objekte binden lassen. Stichwort hierzu ist das ominöse this, das schon manchen JavaScript-Entwickler zur Verzweiflung gebracht hat. Ohne allzu weit auszuholen, ist this eine Referenz auf den aktuellen Objektkontext zum Ausführungszeitpunkt (Listing 3).

 

var greeter = function() {
  console.log('Hello, I am ' + this.name);
},
luke = { name: 'Luke' },
han  = { name: 'Han' };

greeter.call(luke); // Hello, I am Luke
greeter.call(han); // Hello, I am Han


Diese Möglichkeit gibt es in PHP 5.3 generell nicht. Ab PHP 5.4 hat der Entwickler aber die Möglichkeit, die JavaScript-Funktionalität nachzuimplementieren. Closure::bindTo() bindet eine anonyme Funktion an ein bestehendes Objekt. Das zurückgegebene Closure-Objekt hat jetzt Zugriff auf $this als Referenz zum gebundenen Objekt (Listing 4).

// PHP 5.4
$greeter = function() {
  echo 'Hello, I am '.$this->name;
};

class Hero {
  public $name;
  public function __construct($name) {
    $this->name = $name;
  }
}

$luke = new Hero('Luke');
$lukeGreets = $greeter->bindTo($luke);
$lukeGreets(); // Hello, I am Luke

$han = new Hero('Han');
$hanGreets = $greeter->bindTo($han);
$hanGreets(); // Hello, I am Han

Mit PHP 5.4 wurde die Closure-Klasse um zwei Methoden zur Steuerung dieser Bindung erweitert. Closure::bindTo($newThis, $newScope = ’static‘) ist eine Instanzmethode auf der Closure, während Closure::bind($closure, $newThis, $newScope = ’static‘) einfach eine statische Methode mit derselben Funktionalität ist. Wichtig zu wissen ist, dass nur die von den Methoden zurückgegebenen Funktionen gebunden sind, d. h. es wird ein gebundener Klon der Originalfunktion zurückgegeben.

bind() und bindTo() erlauben aber nicht nur die Bindung einer anonymen Funktion an eine Objektinstanz, sondern machen auch die Steuerung der Zugriffsberechtigungen auf private– und protected-Eigenschaften und -Methoden möglich. Über den zweiten (dritten) Parameter kann hierzu der Klassen-Scope gesetzt werden. Mithilfe des Klassen-Scopes verhält sich eine anonyme Funktion so, als wäre sie eine Methode der angegebenen Klasse (Listing 5).

class A {
  private $private     = 'I am private';
  protected $protected = 'I am protected';
  public $public       = 'I am public';
}class B extends A { }

$accessor = function($var) {
  var_dump($this->$var);
};

$a = new A();
$aAccessor = $accessor->bindTo($a);
$aAccessor('public');    // I am public
$aAccessor('protected'); // Fatal error: Cannot access protected property A::$protected
$aAccessor('private');   // Fatal error: Cannot access private property A::$private

$aAccessor = $accessor->bindTo($a, 'A');
$aAccessor('public');    // I am public
$aAccessor('protected'); // I am protected
$aAccessor('private');   // I am private

$b = new B();
$bAccessor = $accessor->bindTo($b);
$bAccessor('public');    // I am public
$bAccessor('protected'); // Fatal error: Cannot access protected property B::$protected
$bAccessor('private');   // Notice: Undefined property: B::$private

$bAccessor = $accessor->bindTo($b, 'B');
$bAccessor('public');    // I am public
$bAccessor('protected'); // I am protected
$bAccessor('private');   // Notice: Undefined property: B::$private

$bAccessor = $accessor->bindTo($b, 'A');
$bAccessor('public');    // I am public
$bAccessor('protected'); // I am protected
$bAccessor('private');   // I am private

Wichtig beim Binden einer anonymen Funktion an eine Objektinstanz ist, dass das Binden allein (mit dem ersten Parameter) den Klassen-Scope nicht automatisch setzt. Eine an eine Objektinstanz gebundene anonyme Funktion, deren Klassen-Scope nicht gesetzt wurde, wird intern auf einen undefinierten Klassen-Scope gesetzt und hat daher nur Zugriff auf public-Member des gebundenen Objekts. Der Einfachheit halber erlaubt PHP auch die Angabe einer Objektinstanz für den Klassen-Scope:

$a = new A();
$aAccessor = $accessor->bindTo($a, $a);

Der zweite Parameter von Closure::bindTo() darf auch eine Objektinstanz sein, was dasselbe wäre wie $accessor->bindTo($a, get_class($a)). Durch das Setzen des Klassen-Scopes wird auch der Zugriff auf statische Methoden und Eigenschaften mithilfe von self ermöglicht (Listing 6).

class A {
  private static $private     = 'I am private';
  protected static $protected = 'I am protected';
  public static $public       = 'I am public';
}

class B extends A { }

$accessor = function($var) {
  echo self::$$var;
};

$accessor('public'); // Fatal error: Cannot access self:: when no class scope is active

$aAccessor = $accessor->bindTo(null, 'A');
$aAccessor('public');    // I am public
$aAccessor('protected'); // I am protected
$aAccessor('private');   // I am private

$bAccessor = $accessor->bindTo(null, 'B');
$bAccessor('public');    // I am public
$bAccessor('protected'); // I am protected
$bAccessor('private');   // Fatal error: Cannot access  property B::$private

In Zusammenhang mit dem Zugriff auf statische Member sei noch erwähnt, dass es PHP ab 5.4 erlaubt, das Binden von anonymen Funktionen an Objektinstanzen mit dem static-Keyword zu verbieten. Das Ändern des Klassen-Scopes bleibt natürlich weiterhin möglich. Anonyme Funktionen mit dem static-Keyword können indes nicht an Objektinstanzen gebunden werden:

$accessor = static function($var) {
  echo self::$$var;
};

$aAccessor = $accessor->bindTo(new A(), 'A');
// Warning: Cannot bind an instance to a static closure in ...

[ header = Anwendungsbeispiele ]

Anwendungsbeispiele

Grundsätzlich sind anonyme Funktionen überall dort einsetzbar, wo interne PHP-Funktionen einen Parameter des Typs callback erlauben. Klassische Kandidaten sind hierbei die diversen Sortierfunktionen für PHP-Arrays usort(), uasort() und uksort().

Aus dem Bereich der funktionalen Programmiersprachen kommen Konzepte wie Filter, Map und Reduce (Kasten: „Filter, Map und Reduce“), die auch PHP schon seit Längerem mit den Funktionen array_filter(), array_map() und array_reduce() unterstützt. Ohne anonyme Funktionen waren diese jedoch kaum benutzbar, bzw. nicht flexibel genug einsetzbar. Auch aus der funktionalen Programmierung kommt das Konzept der Callback-Iteration, d. h. die Iteration über eine Liste erfolgt mit einer Callback-Funktion, die für jedes Element aufgerufen wird. Wenn eine solche Funktionalität benötigt wird, kann array_walk(), array_walk_recursive() oder iterator_apply() verwendet werden.

Während alle vorgenannten Beispiele auch in PHP < 5.3 mit anderen callback-Typen umsetzbar sind, ist die Verwendung mithilfe von anonymen Funktionen wesentlich einfacher und flexibler. Daneben gibt es nun auch noch einige Pattern, die aus der funktionalen Programmierung kommen und deren Verwendung in PHP durchaus Sinn ergeben kann.

Dieser Abschnitt hat bis jetzt keine Beispiele enthalten, da in der PHP-Dokumentation für die angesprochenen Funktionen viel Informationsmaterial verfügbar ist. Trotzdem wollen wir uns zum Abschluss noch ein Beispiel ansehen, in dem array_filter(), array_map() und array_reduce() gemeinsam verwendet werden (Listing 7).

class MessageQueue {
  public function isActive() {}
  public function isEmpty() {}
}

function allQueuesEmpty(array $queues) {
  return array_reduce(
    array_map(
      function(MessageQueue $q) {
        return $q->isEmpty();
      },
      array_filter(
        $queues,
        function(MessageQueue $q) {
          return $q->isActive();
        }
      )
    ),
    function($empty, $previous) {
      return $empty && $previous;
    },
    true
  );
}

allQueuesEmpty() kann auf ein Array von MessageQueue-Objekten angewendet werden. Die Funktion ist von innen nach außen zu lesen: array_filter() entfernt alle nicht-aktiven Queues aus der Liste, array_map() mappt die Liste aus aktiven Queues auf ihren empty-Status und array_reduce() kombiniert dann den empty-Status mit einem logischen „und“ zum finalen Status.

Rekursion

Auch anonyme Funktionen lassen sich rekursiv aufrufen – allerdings ist hierzu ein kleiner Trick notwendig. Die Fibonacci-Folge soll uns als einfaches Beispiel dienen:

$fib = function ($num) use (&$fib) {
  if ($num <= 0) {
    return 0;
  } if ($num <= 2) {
    return 1;
  }
  return $fib($num - 2) + $fib($num - 1);
};

Der entscheidende „Trick“ bei der Rekursion von anonymen Funktionen ist die Übergabe einer Referenz auf die Funktionsvariable mittels use, um eine Closure zu erstellen. Ohne ein „pass-by-reference“ wäre es nicht möglich, die noch nicht existierende Variable $fib in den Closure-Kontext zu übernehmen.

Currying

Currying (auch „Schönfinkeln“) ist ein Konzept der funktionalen Programmierung, bei dem eine Funktion mit mehreren Argumenten durch eine Verkettung von Funktionen mit je einem Argument abgebildet wird. Abgesehen von der formalen Definition, kann man Operationen, bei denen eine bestimmte Anzahl von Funktionsargumenten vorbelegt wird, als Currying bezeichnen. Der Anwendungsfall in PHP hierfür kann z. B. sein, dass man bei bestehenden Funktionen die Anzahl der Funktionsargumente reduziert und so spezialisierte parametrisierte Funktionen generiert:

function createMatcher($pattern) {
  return function($input) use ($pattern) {
    return preg_match($pattern, $input);
  };
}

$list     = array('a', 'b', 'c', 'd');
$filtered = array_filter($list, createMatcher('/[a-c]/'));

createMatcher() erstellt eine Callback-Funktion für array_filter(), die nur Elemente zurückgibt, die auf ein bestimmtes Pattern matchen.

[ header = Memoization ]

Memoization

Memoization (Memoisation) ist ein Konzept, bei dem beliebige Funktionen um eine Art internen Cache erweitert werden, um die Anzahl von komplexen Berechnungen oder rekursiven Aufrufen zu reduzieren. Als Beispiel muss hier wieder die gute alte Fibonacci-Folge herhalten. Wir verwenden dieselbe rekursive Funktion wie oben bereits erläutert. Listing 8 zeigt einen ersten Versuch einer Funktion, die unsere Fibonacci-Funktion cachen soll.

function cacheFib($func) {
  $memory = array();
  return function($num) use (&$memory, $func) {
    if (!array_key_exists($num, $memory)) {
      $memory[$num] = $func($num);
    }
    return $memory[$num];
  };
}

$cachedFib = cacheFib($fib);

Was haben wir gemacht? cacheFib() erstellt ein einfaches Array zum Speichern unserer bereits berechneten Werte und gibt eine anonyme Funktion zurück, die $memory und die übergebene Funktion in einer Closure einschließt. Die zurückgegebene Funktion enthält nur die Caching-Logik und einen Aufruf der Originalfunktion, sollte kein Wert für den gegebenen Parameter im Cache gefunden werden.

Insbesondere wenn wir die Fibonacci-Funktion öfter mit denselben Parametern aufrufen, nimmt uns der Cache schon einiges an Arbeit ab – aber auch nur dann, denn bei den rekursiven Aufrufen bringt uns der Cache nichts, weil hier stets die Original $fib-Funktion aufgerufen wird. Geht das auch besser? Ja, aber jetzt wird es „tricky“ … Denn jetzt nutzen auch die rekursiven Aufrufe die neue memoisierte Funktion (Listing 9).

function memoizeFib(&$func) {
  $memory = array();
  $func   = function($num) use (&$memory, $func) {
    if (!array_key_exists($num, $memory)) {
      $memory[$num]    = $func($num);
    }
    return $memory[$num];
  };
}
memoizeFib($fib);

memoizeFib() gibt, im Gegensatz zu cacheFib() die neue Funktion nicht als Rückgabewert zurück, sondern überschreibt die als Referenz übergebene Funktion intern durch eine neue Implementierung. Die neue Implementierung nutzt jetzt den Cache genauso wie die vorherige Funktion. Das Entscheidende jedoch ist, dass $fib durch den Aufruf von memoizeFib() vollständig durch eine neue Implementierung ausgetauscht wird, die jedoch im Gegenzug die Originalfunktion über eine Closure im Kontext hält und so im Falle von Cache-Misses aufrufen kann. Damit profitieren jetzt auch die rekursiven Aufrufe vom Caching und die Anzahl der Funktionsaufrufe, die für die Berechnung nötig sind, wird drastisch reduziert. Mit ein klein wenig mehr Aufwand lässt sich aus memoizeFib() auch eine universell einsetzbare Memoisationsfunktion bauen (Listing 10).

function memoize(&$func) {
  $memory = array();
  $func = function() use (&$memory, $func) {
    $args = func_get_args();
    $key  = md5(serialize($args));
    if (!array_key_exists($key, $memory)) {
      $memory[$key] = call_user_func_array($func, $args);
    }
    return $memory[$key];
  };
}

Natürlich macht eine Memoization nur für deterministische Funktionen Sinn, also Funktionen, die für dieselben Eingabeparameter immer dasselbe Ergebnis liefern.

Zum Schluss

Anonyme Funktionen sind eine spannende Erweiterung für PHP und eröffnen den Entwicklern viele neue Optionen in der Entwicklung. Wenn man nicht dem zwanghaften Drang verfällt jetzt alles irgendwie über Callbacks und anonyme Funktionen lösen zu wollen, bekommt man als Entwickler ein neues sinnvolles Werkzeug in die Toolbox, das manches Problem einfacher und eleganter lösen lässt. Insbesondere auch erfahrene JavaScript-Entwickler können jetzt bestimmte elegante Konzepte und Pattern aus JavaScript ins PHP-Umfeld übertragen. Aber natürlich gilt auch hier: „with great power comes great responsibility“ [Voltaire. Jean, Adrien. Beuchot, Quentin and Miger, Pierre, Auguste. „Œuvres de Voltaire, Volume 48“. Lefèvre, 1832], denn Code, der zu sehr auf die Verwendung von anonymen Funktionen und Callbacks aufbaut, wird schnell unübersichtlich und schwer zu debuggen. Anonyme Funktionen sind kein Allheilmittel und keine Wunderwaffe im Kampf gegen die Probleme der Softwareentwicklung, richtig eingesetzt machen sie es aber dem Entwickler an vielen Stellen leichter und erlauben flexiblere Implementierungen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -