rfc:argument_unpacking

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:argument_unpacking [2013/08/30 19:08] nikicrfc:argument_unpacking [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 2: Line 2:
   * Date: 2013-08-30   * Date: 2013-08-30
   * Author: Nikita Popov <nikic@php.net>   * Author: Nikita Popov <nikic@php.net>
-  * Status: Under Discussion +  * Status: Implemented (in PHP 5.6) 
-  * Proposed for: PHP 5.6 +  * Patch: https://github.com/php/php-src/pull/477
-  * Patch: https://github.com/nikic/php-src/compare/variadics...splat+
   * Mailing list discussion: http://markmail.org/message/dxae5ybjldg6pftp   * Mailing list discussion: http://markmail.org/message/dxae5ybjldg6pftp
  
Line 40: Line 39:
 </code> </code>
  
-It's possible to use ''%%...%%'' multiple times in a call and it is possible to mix it with normal arguments:+It's possible to use ''%%...%%'' multiple times in a call and it is possible to use normal arguments before argument unpacking:
  
 <code php> <code php>
-test(1, 2, ...[3, 4], 5, 6, ...[7, 8]); // [1, 2, 3, 4, 5, 6, 78]+$args1 = [1, 2, 3]; 
 +$args2 = [4, 5, 6]; 
 +test(...$args1, ...$args2); // [1, 2, 3, 4, 5, 6
 +test(1, 2, 3, ...$args2);   // [1, 2, 3, 4, 5, 6
 +</code> 
 + 
 +Howeverit is not possible to use normal arguments after argument unpacking was used. Both of the following are invalid: 
 + 
 +<code php> 
 +test(...$args14, 5, 6); 
 +test(...$args1, 4, 5, 6, ...$args2);
 </code> </code>
  
Line 71: Line 80:
  
 <code php> <code php>
-var_dump(1, 2, ...null, 3, 4);+var_dump(1, 2, ...null, ...[3, 4]);
 // Warning: Only arrays and Traversables can be unpacked // Warning: Only arrays and Traversables can be unpacked
 // int(1) int(2) int(3) int(4) // int(1) int(2) int(3) int(4)
Line 90: Line 99:
 </code> </code>
  
-By-reference passing will not work if the unpacked entity is a Traversable. Instead an exception is thrown on encountering the first argument that would require by-reference passing:+By-reference passing will not work if the unpacked entity is a Traversable. Instead an ''E_WARNING'' level error is thrown and the argument is passed by-value instead:
  
 <code php> <code php>
 test(...new ArrayIterator([1, 2, 3, 4, 5])); test(...new ArrayIterator([1, 2, 3, 4, 5]));
-// Exception: Cannot pass by-reference argument 3 of test() by unpacking a Traversable+// Warning: Cannot pass by-reference argument 3 of test() by unpacking a Traversable, passing by-value instead
 </code> </code>
  
-The reasons why this is not allowed are two-fold: +The reasons why we can't pass by-reference from a Traversable are two-fold: 
  
   * It's not possible to determine the number of elements in a Traversable ahead of time. As such we can not know whether unpacking the Traversable will or will not hit a by-reference argument.   * It's not possible to determine the number of elements in a Traversable ahead of time. As such we can not know whether unpacking the Traversable will or will not hit a by-reference argument.
   * It's not possible to determine if a Traversable has support for by-reference iteration or if it will trigger an error if this is requested.   * It's not possible to determine if a Traversable has support for by-reference iteration or if it will trigger an error if this is requested.
-   + 
-An exception rather than an error is used here because a) Traversable related code uses exceptions rather than errors and b) the error would either have to be fatal or involve complicated stack cleanup that exceptions do automatically.+==== String keys ==== 
 + 
 +In order to ensure forward-compatibility with [[rfc:named_params|named arguments]] the unpacking operator does not support string keys. If a string key is encountered during unpacking recoverable error is thrown. If the error is ignored using a custom error handler, no further arguments will be unpacked but the call still happens.
  
 ===== Backward Compatibility ===== ===== Backward Compatibility =====
Line 145: Line 156:
  
 Lastly, it seems that people naturally expect that this syntax is present if the variadics syntax is present. So if we implement variadics, it's probably best to include this as well. Lastly, it seems that people naturally expect that this syntax is present if the variadics syntax is present. So if we implement variadics, it's probably best to include this as well.
 +
 +===== Examples =====
 +
 +The code samples in the "Proposal" section are rather technical and not code you would actually write. This section contains a few more practical examples of this feature.
 +
 +==== Extending variadic functions: forwarding ====
 +
 +The introduction already mentioned ''%%$db->query($query, ...$params)%%'' as an example. At this point you could wonder: Why would I want to write code like that? Why should I have the parameters only as an array?
 +
 +One case where this occurs is when extending variadic functions:
 +
 +<code php>
 +class MySqlWithLogging extends MySql {
 +    protected $logger;
 +    public function query($query, ...$params) {
 +        $this->logger->log(
 +            'Running query "%s" with parameters [%s]',
 +            $query, implode(', ', $params)
 +        );
 +        
 +        return parent::query($query, ...$params);
 +    }
 +}
 +</code>
 +
 +The above code sample extends the variadic ''query()'' method with logging and needs to forward all arguments to the parent function.
 +
 +==== Partial application: multiple unpacks ====
 +
 +Some people were wondering on what occasion you would ever want to unpack *two* arguments in one function call. An example of such a usage is "partial application".
 +
 +If you are not familiar with the concept, partial application allows you to "bind" arguments to a function:
 +
 +<code php>
 +$arrayToLower = bind('array_map', 'strtolower');
 +
 +$arrayToLower(['Foo', 'BAR', 'baZ']); // returns ['foo', 'bar', 'baz']
 +
 +// The above $arrayToLower call resolves to:
 +// array_map('strtolower', ['Foo', 'BAR', 'baZ'])
 +</code>
 +
 +This is a common functional paradigm, but rather rarely used in PHP. Anyway, an "old-style" (no variadic syntax, no argument unpacking) definition of the ''bind()'' function would look like this:
 +
 +<code php>
 +function bind(callable $function) {
 +    $boundArgs = array_slice(func_get_args(), 1);
 +    return function() use ($function, $boundArgs) {
 +        return call_user_func_array(
 +            $function, array_merge($boundArgs, func_get_args())
 +        );
 +    }
 +}
 +</code>
 +
 +And the "new-style" definition (with variadic syntax and argument unpacking) looks like this:
 +
 +<code php>
 +function bind(callable $function, ...$boundArgs) {
 +    return function(...$args) use($function, $boundArgs) {
 +        return $function(...$boundArgs, ...$args);
 +    }
 +}
 +</code>
  
 ===== Patch ===== ===== Patch =====
  
-The diff can be found here: https://github.com/nikic/php-src/compare/variadics...splat+The patch for this features is available as a PR: https://github.com/php/php-src/pull/477 
 + 
 +===== Vote ===== 
 + 
 +As this is a language change a two third majority is required. 
 + 
 +Voting started 2013-12-21 and ended 2014-01-11.
  
-The patch is based off the variadics implementation, but can also be implemented without it.+<doodle title="Implement argument unpacking in PHP 5.6?" auth="nikic" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle>
  
 ===== Support in other languages ===== ===== Support in other languages =====
rfc/argument_unpacking.1377889686.txt.gz · Last modified: 2017/09/22 13:28 (external edit)