rfc:argument_unpacking
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
Previous revisionNext revision | |||
— | rfc:argument_unpacking [2013/12/21 12:49] – Start voting nikic | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== PHP RFC: Argument Unpacking ====== | ||
+ | * Date: 2013-08-30 | ||
+ | * Author: Nikita Popov < | ||
+ | * Status: In Voting | ||
+ | * Proposed for: PHP 5.6 | ||
+ | * Patch: https:// | ||
+ | * Mailing list discussion: http:// | ||
+ | ===== Introduction ===== | ||
+ | |||
+ | This RFC complements the [[rfc: | ||
+ | |||
+ | As a usage example, consider a variadic method '' | ||
+ | |||
+ | <code php> | ||
+ | call_user_func_array([$db, | ||
+ | </ | ||
+ | |||
+ | This RFC proposes a syntax for unpacking arguments directly in the call syntax: | ||
+ | |||
+ | <code php> | ||
+ | $db-> | ||
+ | </ | ||
+ | |||
+ | ===== Proposal ===== | ||
+ | |||
+ | An argument in a function call that is prefixed by '' | ||
+ | |||
+ | As such all of the following function calls are equivalent: | ||
+ | |||
+ | <code php> | ||
+ | function test(...$args) { var_dump($args); | ||
+ | |||
+ | test(1, 2, 3); // [1, 2, 3] | ||
+ | test(...[1, 2, 3]); // [1, 2, 3] | ||
+ | test(...new ArrayIterator([1, | ||
+ | |||
+ | // Note: It doesn' | ||
+ | // | ||
+ | </ | ||
+ | |||
+ | It's possible to use '' | ||
+ | |||
+ | <code php> | ||
+ | $args1 = [1, 2, 3]; | ||
+ | $args2 = [4, 5, 6]; | ||
+ | test(...$args1, | ||
+ | test(1, 2, 3, ...$args2); | ||
+ | test(...$args1, | ||
+ | </ | ||
+ | |||
+ | The '' | ||
+ | |||
+ | <code php> | ||
+ | fn(...$args); | ||
+ | $fn(...$args); | ||
+ | $obj-> | ||
+ | ClassName:: | ||
+ | new ClassName(...$args); | ||
+ | </ | ||
+ | |||
+ | Argument unpacking is not limited to variadic functions, it can also be used on " | ||
+ | |||
+ | <code php> | ||
+ | function test($arg1, $arg2, $arg3 = null) { | ||
+ | var_dump($arg1, | ||
+ | } | ||
+ | |||
+ | test(...[1, 2]); // 1, 2 | ||
+ | test(...[1, 2, 3]); // 1, 2, 3 | ||
+ | test(...[1, 2, 3, 4]); // 1, 2, 3 (remaining arg is not captured by the function declaration) | ||
+ | </ | ||
+ | |||
+ | If you try to unpack something that is not an array or Traversable a warning is thrown, but apart from that the call continues as usual: | ||
+ | |||
+ | <code php> | ||
+ | var_dump(1, 2, ...null, 3, 4); | ||
+ | // Warning: Only arrays and Traversables can be unpacked | ||
+ | // int(1) int(2) int(3) int(4) | ||
+ | </ | ||
+ | |||
+ | ==== By-reference passing ==== | ||
+ | |||
+ | If an array is unpacked the elements will by passed by-value/ | ||
+ | |||
+ | <code php> | ||
+ | function test($val1, $val2, & | ||
+ | foreach ($refs as &$ref) ++$ref; | ||
+ | } | ||
+ | |||
+ | $array = [1, 2, 3, 4, 5]; | ||
+ | test(...$array); | ||
+ | var_dump($array); | ||
+ | </ | ||
+ | |||
+ | By-reference passing will not work if the unpacked entity is a Traversable. Instead an '' | ||
+ | |||
+ | <code php> | ||
+ | test(...new ArrayIterator([1, | ||
+ | // Warning: Cannot pass by-reference argument 3 of test() by unpacking a Traversable, | ||
+ | </ | ||
+ | |||
+ | 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 if a Traversable has support for by-reference iteration or if it will trigger an error if this is requested. | ||
+ | |||
+ | ==== String keys ==== | ||
+ | |||
+ | In order to ensure forward-compatibility with [[rfc: | ||
+ | |||
+ | ===== Backward Compatibility ===== | ||
+ | |||
+ | This change does not break userland or internal compatibility. | ||
+ | |||
+ | ===== Advantages over call_user_func_array ===== | ||
+ | |||
+ | Usage of '' | ||
+ | |||
+ | <code php> | ||
+ | call_user_func_array([$db, | ||
+ | // vs | ||
+ | $db-> | ||
+ | </ | ||
+ | |||
+ | '' | ||
+ | |||
+ | '' | ||
+ | |||
+ | <code php> | ||
+ | (new ReflectionClass(' | ||
+ | // vs | ||
+ | new ClassName(...$args); | ||
+ | </ | ||
+ | |||
+ | Futhermore '' | ||
+ | |||
+ | <code php> | ||
+ | switch (count($args)) { | ||
+ | case 0: $func(); break; | ||
+ | case 1: $func($args[0]); | ||
+ | case 2: $func($args[0], | ||
+ | case 3: $func($args[0], | ||
+ | case 4: $func($args[0], | ||
+ | case 5: $func($args[0], | ||
+ | default: call_user_func_array($func, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | The '' | ||
+ | |||
+ | 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 " | ||
+ | |||
+ | ==== Extending variadic functions: forwarding ==== | ||
+ | |||
+ | The introduction already mentioned '' | ||
+ | |||
+ | One case where this occurs is when extending variadic functions: | ||
+ | |||
+ | <code php> | ||
+ | class MySqlWithLogging extends MySql { | ||
+ | protected $logger; | ||
+ | public function query($query, | ||
+ | $this-> | ||
+ | ' | ||
+ | $query, implode(', | ||
+ | ); | ||
+ | | ||
+ | return parent:: | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | The above code sample extends the variadic '' | ||
+ | |||
+ | ==== Partial application: | ||
+ | |||
+ | 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 " | ||
+ | |||
+ | If you are not familiar with the concept, partial application allows you to " | ||
+ | |||
+ | <code php> | ||
+ | $arrayToLower = bind(' | ||
+ | |||
+ | $arrayToLower([' | ||
+ | |||
+ | // The above $arrayToLower call resolves to: | ||
+ | // array_map(' | ||
+ | </ | ||
+ | |||
+ | This is a common functional paradigm, but rather rarely used in PHP. Anyway, an " | ||
+ | |||
+ | <code php> | ||
+ | function bind(callable $function) { | ||
+ | $boundArgs = array_slice(func_get_args(), | ||
+ | return function() use ($function, $boundArgs) { | ||
+ | return call_user_func_array( | ||
+ | $function, array_merge($boundArgs, | ||
+ | ); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | And the " | ||
+ | |||
+ | <code php> | ||
+ | function bind(callable $function, ...$boundArgs) { | ||
+ | return function(...$args) use($function, | ||
+ | return $function(...$boundArgs, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Normal arguments after unpacked arguments ==== | ||
+ | |||
+ | Some people also had doubts regarding the usefulness of allowing '' | ||
+ | |||
+ | One use-case for this pattern are the variants of '' | ||
+ | |||
+ | <code php> | ||
+ | array_udiff(...$arrays, | ||
+ | array_udiff_uassoc(...$arrays, | ||
+ | </ | ||
+ | |||
+ | While this kind of API was undoubtedly a bad choice in the first place, I see little reason not to support calls to them. | ||
+ | |||
+ | Another use case is appending one additional argument to a variadic argument list. For example, the following snippet adds an additional '' | ||
+ | |||
+ | <code php> | ||
+ | $this-> | ||
+ | </ | ||
+ | |||
+ | ===== Patch ===== | ||
+ | |||
+ | The patch for this features is available as a PR: https:// | ||
+ | |||
+ | ===== Vote ===== | ||
+ | |||
+ | As this is a language change a two third majority is required. | ||
+ | |||
+ | Voting started 2013-12-21 and will be open for at least two weeks (holidays :) | ||
+ | |||
+ | <doodle title=" | ||
+ | * Yes | ||
+ | * No | ||
+ | </ | ||
+ | |||
+ | ===== Support in other languages ===== | ||
+ | |||
+ | This feature is supported by many languages. Some of the more important ones being: | ||
+ | |||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | * Java supports this, but only for variadic parameters and without any special syntax (type based) | ||
+ | * JavaScript ([[http:// |
rfc/argument_unpacking.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1