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/08/30 20:00] – add a few larger examples nikic | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== PHP RFC: Argument Unpacking ====== | ||
+ | * Date: 2013-08-30 | ||
+ | * Author: Nikita Popov < | ||
+ | * Status: Under Discussion | ||
+ | * 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> | ||
+ | test(1, 2, ...[3, 4], 5, 6, ...[7, 8]); // [1, 2, 3, 4, 5, 6, 7, 8] | ||
+ | </ | ||
+ | |||
+ | 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 exception is thrown on encountering the first argument that would require by-reference passing: | ||
+ | |||
+ | <code php> | ||
+ | test(...new ArrayIterator([1, | ||
+ | // Exception: Cannot pass by-reference argument 3 of test() by unpacking a Traversable | ||
+ | </ | ||
+ | |||
+ | The reasons why this is not allowed 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. | ||
+ | | ||
+ | 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. | ||
+ | |||
+ | ===== 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, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Patch ===== | ||
+ | |||
+ | The diff can be found here: https:// | ||
+ | |||
+ | The patch is based off the variadics implementation, | ||
+ | |||
+ | ===== 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