메아리 저널

PHP에서 "가짜" 클로저 쓰기

PHP는 언어적인 측면으로 봤을 때 쓸데 없는 기능(예: register_globals)이 많고 정작 있으면 좋을 기능들이 쓸데 없이 부족(예: 유니코드 지원)한 문제로 여러 개발자들을 괴롭히곤 한다. 후자에 속하는 한 가지 기능으로는 클로저가 있는데, 말하자면:

stack := fun
    var data = []
    return {
        empty: fun -> data.empty?,
        top: fun -> data[-1],
        push: fun v -> data.append(v),
        pop: fun v -> data.remove(-1),
    }
end

st = stack()
println st[:empty]()   --> true
st[:push](42)
println st[:empty]()   --> false
st[:push](54)
println st[:pop]()     --> 54
println st[:top]()     --> 42

이런 식으로 scope를 빠져 나간 뒤에도 그 scope 안에 잡혀 있는 변수들은 해당 scope를 더 이상 사용하지 않을 때까지 유지되도록 하는 컨셉이다. (오랜만에 나루 코드 좀 써 봤다. 저 부분 문법은 이제 거의 확정이다.)

간만에 PHP로 비슷한 걸 짜 봤으면 좋겠다 하는 생각이 들어서 잠시 고민하다가, create_function과 정적(static) 변수를 응용하면 될 것 같다! 라는 생각이 들어서 급히 짜 본 게 다음과 같은 코드.

function outer($arg1, $arg2, &$local1, &$local2) {
    $ret = array($local1, $local2);
    $local1 = $arg1; $local2 = $arg2;
    return $ret;
}

function create_closure_outer() {
    return create_function('$arg1, $arg2',
        'static $local1 = 1, $local2 = 2;
         return outer($arg1, $arg2, $local1, $local2);');
}

$outer = create_closure_outer();
var_dump($outer(3, 9));    // array(1, 2)
$outer2 = create_closure_outer();
var_dump($outer2(7, 6));   // array(1, 2)
var_dump($outer(15, 10));  // array(3, 9)
var_dump($outer2(0, 0));   // array(7, 6)

…좀 변태적인 코드기도 하고 너무 많은 클로저가 존재하게 되면 메모리를 열심히 잡아 먹는 괴물이 될 수도 있지만1 적절히 쓰면 유용할 것이다. 일반화시키기도 어렵지 않을 듯. (PHP5의 Reflection API를 쓰면 될 거다. 아마도)

이런 게 어디 쓸모가 있을까 싶어서 설명하면: 이 코드는 원래 preg_replace_callback의 인자로 쓸려고 만든 건데, 단 하나의 함수를 위해 state를 담는 객체를 만들고 하는 게 너무 번잡해서 그랬다. 이 코드는 현재 메아리 내부 스크립트에 포함되어서 잘 돌아 가고 있다.

…근데 이런 코드 짜느니 그냥 PHP를 버리는 게 낫겠다.


  1. PHP의 함수는 한 번 생성되면 삭제되지 않는다. create_function으로 생성된 것도 마찬가지.

이 글은 본래 http://mearie.org/journal/2008/04/php-pseudo-closure에 썼던 것을 옮겨 온 것입니다.


(rev 797ba6fb3720)