PHP中的惰性函数定义-有可能吗?
在JavaScript中,可以通过仅在第一次调用函数时执行昂贵的一次性操作,使用惰性函数定义来优化对函数的第2个Nth调用。
我想在PHP 5中做同样的事情,但是不允许重新定义函数,也不允许重载函数。
实际上,我要执行的操作如下所示,仅对其进行了优化,因此第2个Nth调用(例如25-100)不需要重新检查它们是否是第一个调用。
$called = false; function foo($param_1){ global $called; if($called == false){ doExpensiveStuff($param_1); $called = true; } echo '<b>'.$param_1.'</b>'; }
PS我曾经考虑过将include_once()或者require_once()作为函数中的第一行来执行一次外部代码,但是我听说它们也很昂贵。
有任何想法吗?还是有更好的方法来解决这个问题?
解决方案
我们是否实际配置了此代码?我怀疑额外的布尔测试是否会对页面呈现时间产生任何可衡量的影响。
我们可以进行条件函数定义。
if( !function_exists('baz') ) { function baz( $args ){ echo $args; } }
但是目前,函数在定义时就变成了砖头。
我们可以使用create_function
,但我建议我们不要使用它,因为它速度慢,占用大量内存,直到php退出后才会获得free(),并且是一个与eval()一样大的安全漏洞。
等到PHP5.3,我们在这里有了" closures" http://wiki.php.net/rfc/closures
那你就可以做
if( !isset( $baz ) ) { $baz = function( $args ) { echo $args; } } $baz('hello'); $baz = function( $args ) { echo $args + "world"; } $baz('hello');
进一步阅读后,这就是我们想要的效果。
$fname = 'f_first'; function f_first( $even ) { global $fname; doExpensiveStuff(); $fname = 'f_others'; $fname( $even ); /* code */ } function f_others( $odd ) { print "<b>".$odd."</b>"; } foreach( $blah as $i=>$v ) { $fname($v); }
它会做我们想要的事情,但是该调用可能比普通的函数调用贵一些。
在PHP5.3中,这也应有效:
$func = function( $x ) use ( $func ) { doexpensive(); $func = function( $y ) { print "<b>".$y."</b>"; } $func($x); } foreach( range(1..200) as $i=>$v ) { $func( $v ); }
(就我个人而言,我当然认为所有这些巧妙的窍门在速度上将比我们之前对2个正位的比较要慢。))
如果我们真的担心到处都获得最佳速度
$data = // some array structure doslowthing(); foreach( $data as $i => $v ) { // code here }
但是,我们可能无法执行此操作,但是我们没有足够的空间来阐明。但是,如果我们可以这样做,那么简单的答案通常是最好的:)
如果确实发现额外的布尔测试太昂贵了,则可以将变量设置为函数的名称并调用它:
$func = "foo"; function foo() { global $func; $func = "bar"; echo "expensive stuff"; }; function bar() { echo "do nothing, i guess"; }; for($i=0; $i<5; $i++) { $func(); }
试一试
我们有任何理由采用功能性样式模式吗?尽管具有匿名函数和关闭计划,PHP确实不是一种功能语言。在这里,类和对象似乎是更好的解决方案。
Class SomeClass{ protected $whatever_called; function __construct(){ $this->called = false; } public function whatever(){ if(!$this->whatever_called){ //expensive stuff $this->whatever_called = true; } //rest of the function } }
如果想花哨的话,可以使用魔术方法来避免必须预先定义被调用的布尔值。如果我们不想实例化一个对象,则将其静态化。
请不要使用include()
或者include_once()
,除非我们不在乎include()
是否失败。如果我们包含代码,那么我们会在意。始终使用require_once()
。
使用本地静态变量:
function foo() { static $called = false; if ($called == false) { $called = true; expensive_stuff(); } }
避免为此使用全局变量。它会使全局名称空间混乱,并使函数的封装性降低。如果除函数内部之外的其他地方都需要知道是否已调用该函数,则值得将此函数放入Alan Storm这样的类中。
PHP没有词法范围,因此我们无法使用函数来完成我们想做的事情。但是,PHP具有类,从概念上讲,为此目的,类的工作方式完全相同。
在javascript中,我们可以执行以下操作:
var cache = null; function doStuff() { if (cache == null) { cache = doExpensiveStuff(); } return cache; }
使用类(在PHP中),我们可以执行以下操作:
class StuffDoer { function doStuff() { if ($this->cache == null) { $this->cache = $this->doExpensiveStuff(); } return $this->cache; } }
是的,基于类的oop比函数式编程更为冗长,但是在性能方面,它们应该差不多。
除此之外,PHP 5.3可能会获得词法作用域/闭包支持,因此,当出现这种情况时,我们可以使用更流畅的函数式编程风格进行编写。有关此功能的详细说明,请参见PHP rfc-wiki。
如何使用局部静态变量?
function doStuff($param1) { static $called = false; if (!$called) { doExpensiveStuff($param1); $called = true; } // do the rest }
如果我们只需要为给定的参数值做一次昂贵的工作,则可以使用数组缓冲区:
function doStuff($param1) { static $buffer = array(); if (!array_key_exists($param1, $buffer)) { doExpensiveStuff($param1); $buffer[$param1] = true; } // do the rest }
局部静态变量在函数调用之间是持久的。他们记得退货后的价值。