Ленивое определение функции в PHP - возможно ли это? - PullRequest
5 голосов
/ 23 сентября 2008

В JavaScript вы можете использовать Ленивые определения функций для оптимизации 2-го - N-го вызова функции, выполняя дорогие разовые операции только при первом вызове функции .

Я бы хотел сделать то же самое в PHP 5, но переопределение функции не допускается, равно как и перегрузка функции.

По сути, я хотел бы сделать следующее: оптимизировать его только так, чтобы вызовы 2-го и N-го (скажем, 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 () в качестве первой строки в функции для выполнения внешнего кода только один раз, но я слышал, что они тоже дороги.

Есть идеи? или есть лучший способ справиться с этим?

Ответы [ 8 ]

13 голосов
/ 23 сентября 2008

Использовать локальную статическую переменную:

function foo() {
    static $called = false;
    if ($called == false) {
        $called = true;
        expensive_stuff();
    }
}

Избегайте использования глобальных для этого. Это загромождает глобальное пространство имен и делает функцию менее инкапсулированной. Если другие места, кроме внутренних функций, должны знать, была ли она вызвана, то стоило бы поместить эту функцию в класс, подобный указанному Alan Storm.

6 голосов
/ 23 сентября 2008

Вы действительно профилировали этот код? Я сомневаюсь, что дополнительный логический тест окажет какое-либо ощутимое влияние на время отрисовки страницы.

4 голосов
/ 23 сентября 2008

Вы можете сделать условное определение функции.

if( !function_exists('baz') )
{ 
    function baz( $args ){ 
        echo $args; 
    }
}

Но в настоящее время функция становится кирпичом, когда она определена.

Вы можете использовать create_function, но я бы посоветовал вам DONT , потому что он медленный, использует много памяти, не освобождается () до выхода php и является дырой в безопасности такой же большой, как eval ().

Подождите до PHP5.3, где у нас есть "замыкания" 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 
}

Возможно, вы не сможете сделать это, но у вас недостаточно возможностей для разъяснения. Однако, если вы можете сделать это, тогда простые ответы часто являются лучшими:)

1 голос
/ 23 сентября 2008

Пожалуйста, не используйте include() или include_once(), если вам все равно, если include() не удастся. Если вы включаете код, то вам все равно. Всегда используйте require_once().

0 голосов
/ 23 сентября 2008

Как насчет использования локальных статических переменных?

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
}

Локальные статические переменные являются постоянными при вызовах функций. Они запоминают значение после возврата.

0 голосов
/ 23 сентября 2008

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

Да, основанная на классе работа более многословна, чем функциональное программирование, но с точки зрения производительности они должны быть примерно одинаковыми.

Помимо всего этого, PHP 5.3, вероятно, получит поддержку лексического контекста / замыкания, поэтому, когда это произойдет, вы сможете писать в более свободном стиле функционального программирования. См. PHP rfc-wiki для подробного описания этой функции .

0 голосов
/ 23 сентября 2008

По какой причине вы привержены функциональному стилю? Несмотря на наличие анонимных функций и планов закрытия, 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
    }
} 

Если вы хотите получить фантазию, вы можете использовать магические методы, чтобы избежать необходимости предопределять вызываемые логические значения. Если вы не хотите создавать экземпляр объекта, переходите на статичную работу.

0 голосов
/ 23 сентября 2008

Если вы обнаружите, что дополнительный логический тест будет слишком дорогим, вы можете присвоить переменной имя функции и вызвать ее:

$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();
}

Дайте это выстрел

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...