PHP에서 커링 currying 함수 작성하기

JavaScript에서 커링 currying 함수 작성하기를 다시 보다가 PHP로도 작성해봤다.

function curry($fn) {
    $arity = (new ReflectionFunction($fn))->getNumberOfParameters();

    return ($resolver = function (...$memory) use ($fn, $arity, &$resolver) {
        return function (...$args) use ($fn, $arity, $resolver, $memory) {
            $local = array_merge($memory, $args);
            $next = count($local) >= $arity ? $fn : $resolver;
            return $next(...$local);
        };
    })();
}

js 버전도 요즘 스타일로 작성하면 좀 더 간결할 것 같다. php와 js와의 차이점을 적어보면,

  • 함수에서 몇 개의 파라미터를 사용하는지 알아내기 위해 리플렉션을 사용했다.
  • js는 lexical scoping이지만 php에서는 스코프 내에서 사용할 컨텍스트를 명시적으로 적어줘야 한다.
  • 함수 내에서 named function을 선언할 수 있지만 이 함수에는 인자로 전달하지 않는 이상 스코프를 공유할 방법이 없다. 대신에 아직 선언되지 않은 변수명을 레퍼런스로 선언 내에 전달하고(&$resolver) $resolver에 익명 함수를 저장하는 것으로 스코프 내로 넣을 수 있다.
  • js에서는 배열도 passed by reference지만 php에서 배열은 passed by value다. 그래서 js 코드처럼 매번 slice 할 필요가 없다.

아래는 함수 작성하면서 사용한 테스트다.

$nullary = curry(function() { return 1; });
assert($nullary() === 1, 'nullary failed');

$unary = curry(function($a) { return $a; });
assert($unary(2) === 2, 'unary with one param failed');

$binary = curry(function($a, $b) { return $a + $b; });
assert($binary(2)(10) === 12, 'binary, one and one param failed case 1');
assert($binary(2)(20) === 22, 'binary, one and one param failed case 2');
assert($binary(2, 20) === 22, 'binary, two param failed');

$ternary = curry(function($a, $b, $c) { return $a + $b + $c; });
assert($ternary(2)(10)(4) === 16, 'ternary, one and one and one param failed');
assert($ternary(2, 20)(2) === 24, 'ternary, two and one param failed');
assert($ternary(4)(2, 20) === 26, 'ternary, one and two param failed case 1');
assert($ternary(4)(4, 20) === 28, 'ternary, one and two param failed case 2');
assert($ternary(4, 4, 20) === 28, 'ternary, three param failed');

function ternary($a, $b, $c) {
    return $a + $b + $c;
}

$namedTernary = curry('ternary');
assert($namedTernary(2)(10)(4) === 16, 'named ternary, one and one and one param failed');
assert($namedTernary(2, 20)(2) === 24, 'named ternary, two and one param failed');
assert($namedTernary(4)(2, 20) === 26, 'named ternary, one and two param failed case 1');
assert($namedTernary(4)(4, 20) === 28, 'named ternary, one and two param failed case 2');
assert($namedTernary(4, 4, 20) === 28, 'named ternary, three param failed');
김용균

안녕하세요, 김용균입니다. 문제를 해결하기 위해 작고 단단한 코드를 작성하는 일을 합니다. 웹의 자유로운 접근성을 좋아합니다. 프로그래밍 언어, 소프트웨어 아키텍처, 커뮤니티에 관심이 많습니다.

이 글 공유하기

이 글이 유익했다면 주변에도 알려주세요!

페이스북으로 공유하기트위터로 공유하기링크드인으로 공유하기Email 보내기

주제별 목록

같은 주제의 다른 글을 읽어보고 싶다면 아래 링크를 확인하세요.

January 27, 2018

Dell Latitude E7240 Archlinux 설치하기

맥북 프로 키보드에 불만족스러워서 그런지 회사에서도 미팅 때마다 사람들이 들고 들어온 노트북을 계속 눈여겨 보게 되었다. 3년 전까지는 델 납품을 받았는데 그 이후로는 레노보를 사용하고 있어서 Dell Latitude랑 XPS, Lenovo Thin…

January 23, 2018

ReactPHP의 Process로 pipe 사용하기

ReactPHP의 child-process 패키지를 사용하면 손쉽게 pipe를 사용할 수 있다. 아래 명령을 코드로 전환한다고 생각해보자. 수작업으로 열어서 pipe를 받아 , 해도 되지만 코드가 복잡해진다. ReactPHP를 사용하면 …