Is it possible to curry method calls in PHP?

前端 未结 8 2438
日久生厌
日久生厌 2020-12-06 01:17

I have a SoapClient instance generated for a WSDL file. All except one of the method invocations require the username and the password to be passed id.

Is there any

相关标签:
8条回答
  • 2020-12-06 01:46

    As mentionned by Ihor, the Non-standard PHP library is interesting. I have already implemented the same method, it is a bit different than Ihor's curried() function

    function curryfy($f, $args = []) {
        $reflexion = new ReflectionFunction($f);
        $nbParams = $reflexion->getNumberOfParameters();
    
        return function (...$arguments) use ($f, $reflexion, $nbParams, $args) {
            if (count($args) + count($arguments) >= $nbParams) {
                return $reflexion->invokeArgs(array_merge($args, $arguments));
            }
    
            return curryfy($f, array_merge($args, $arguments));
        };
    }
    

    Usage :

    function display4 ($a, $b, $c, $d) {
        echo "$a, $b, $c, $d\n";
    };
    $curry4 = curryfy('display4');
    display4(1, 2, 3, 4);
    $curry4(1)(2)(3)(4);
    
    0 讨论(0)
  • 2020-12-06 01:54

    As of php 5.3 you can store an anonymous function in a variable. This anonymous function can call the "original" function with some predefined parameters.

    function foo($x, $y, $z) {
      echo "$x - $y - $z";
    }
    
    $bar = function($z) {
      foo('A', 'B', $z);
    };
    
    $bar('C');
    

    edit: You can also use a closure to parametrise the creation of the anonymous function

    function foo($x, $y, $z) {
      echo "$x - $y - $z";
    }
    
    function fnFoo($x, $y) {
      return function($z) use($x,$y) {
        foo($x, $y, $z);
      };
    }
    
    $bar = fnFoo('A', 'B');
    $bar('C');
    

    edit2: This also works with objects

    class Foo {
      public function bar($x, $y, $z) {
        echo "$x - $y - $z";
      }
    }
    
    function fnFoobar($obj, $x, $z) {
      return function ($y) use ($obj,$x,$z) {
        $obj->bar($x, $y, $z);
      };
    }
    
    $foo = new Foo;
    $bar = fnFoobar($foo, 'A', 'C');
    $bar('B');
    

    But the other suggestions using __call() and a wrapper class may be better if you want to "enhance" a complete class.

    0 讨论(0)
  • 2020-12-06 01:58

    This answer Is it possible to curry method calls in PHP? doesn't show Currying. That answer shows partial application. A nice tutorial which explains the difference between those concepts can be seen here: http://allthingsphp.blogspot.com/2012/02/currying-vs-partial-application.html

    This is Currying:

    function sum3($x, $y, $z) {
        return $x + $y + $z;
    }
    
    // The curried function    
    function curried_sum3($x) {
        return function ($y) use ($x) {
            return function ($z) use ($x, $y) {
                return sum3($x, $y, $z);
            };
        };
    }
    

    Invoking the curried function in PHP 7

    $result = curried_sum3(1)(2)(3); 
    var_dump($result); // int 6
    

    Invoking the curried function in PHP 5

    $f1 = curried_sum3(6);
    $f2 = $f1(6);
    $result = $f2(6);
    var_dump($result);
    
    //OUTPUT:
    int 18
    

    This is Partial application:

    function sum3($x, $y, $z) {
        return $x + $y + $z;
    }
    
    function partial_sum3($x) {
        return function($y, $z) use($x) {
            return sum3($x, $y, $z);
        };
    }
    
    //create the partial
    $f1 = partial_sum3(6);
    //execute the partial with the two remaining arguments
    $result = $f1(6, 6);
    
    var_dump($result);
    
    //OUTPUT:
    int 18
    
    0 讨论(0)
  • 2020-12-06 01:59

    Here is a class implements automatic currying and partial application:

    class lambda
    {
        private $f;
        private $args;
        private $count;
        public function __construct($f, $args = [])
        {
            if ($f instanceof lambda) {
                $this->f = $f->f;
                $this->count = $f->count;
                $this->args = array_merge($f->args, $args);
            }
            else {
                $this->f = $f;
                $this->count = count((new ReflectionFunction($f))->getParameters());
                $this->args = $args;
            }
        }
    
        public function __invoke()
        {
            if (count($this->args) + func_num_args() < $this->count) {
                return new lambda($this, func_get_args());
            }
            else {
                $args = array_merge($this->args, func_get_args());
                $r = call_user_func_array($this->f, array_splice($args, 0, $this->count));
                return is_callable($r) ? call_user_func(new lambda($r, $args)) : $r;
            }
        }
    }
    function lambda($f)
    {
        return new lambda($f);
    }
    

    Example:

    $add = lambda(function($a, $b) { 
        return $a + $b; 
    });
    $add1 = $add(1);
    echo $add1(2); // 3
    

    Even you can do this:

    $int1 = lambda(function($f, $x) {
        return $f($x);
    });
    
    $successor = lambda(function($p, $f, $x) {
        return $f($p($f, $x));
    }); 
    
    $add = lambda(function($p, $q, $f, $x) {
        return $p($f, $q($f, $x));
    }); 
    
    $mul = lambda(function($p, $q, $x) {
        return $p($q($x));
    }); 
    
    $exp = lambda(function($m, $n) {
        return $n($m);
    });
    
    $int2 = $successor($int1);
    $int3 = $add($int1, $int2);
    $int6 = $mul($int3, $int2);
    $int8 = $exp($int2, $int3);
    
    0 讨论(0)
  • 2020-12-06 02:01

    PHP doesn't have currying per se, but you can do something like that in several ways. In your specific case, something like this may work:

    class MySoapClient extends SoapClient {
      ...
      public function __call($meth,$args) {
        if (substr($method,0,5) == 'curry') {
          array_unshift($args,PASSWORD);
          array_unshift($args,USERNAME);
          return call_user_func_array(array($this,substr($meth,5)),$args);
        } else {
          return parent::__call($meth,$args);
        }
      }
    }
    $soapClient = new MySoapClient();
    ...
    // now the following two are equivalent
    $soapClient->currysomeMethod($additionalArg);
    $soapClient->someMethod(USERNAME,PASSWORD,$additionalArg);
    

    Although here's a more general solution for currying in PHP >= 5.3:

    $curriedMethod = function ($additionalArg) use ($soapClient) { return $soapClient->method(USERNAME,PASSWORD,$additionalArg); }
    
    $result = $curriedMethod('some argument');
    
    0 讨论(0)
  • 2020-12-06 02:01

    I did some related research into this today. This is as close as I could get:

    function curryAdd($x)
    {
      return function($y = null) use ($x)
      {
        if (is_null($y)) return $x;
        else return curryAdd($x + $y);
      };
    }
    
    // echo curryAdd(1)(2)(3)(4);
    echo curryAdd(1)
      ->__invoke(2)
      ->__invoke(3)
      ->__invoke(4)
      ->__invoke();
    

    The major problem is PHP will not let you execute a closure directly on a return value (much in the same way PHP will not allow executing a method on an unbound object). However, since closures are an object of type Closure, which have a built-in method __invoke(), the above will work.

    0 讨论(0)
提交回复
热议问题