Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision |
rfc:short-functions [2020/11/04 17:58] – crell | rfc:short-functions [2021/03/26 12:14] – Fixes typo on code snippet nunomaduro |
---|
function add(int $a, int $b): int | function add(int $a, int $b): int |
{ | { |
return $a + b; | return $a + $b; |
} | } |
</code> | </code> |
Functions are simpler than lambdas, as there is no need for closing over variables contextually. Therefore this patch is implemented 100% in the lexer, and thus should have no performance impact whatsoever. | Functions are simpler than lambdas, as there is no need for closing over variables contextually. Therefore this patch is implemented 100% in the lexer, and thus should have no performance impact whatsoever. |
| |
| ==== Consistency with closure syntax ==== |
| |
| This RFC is designed to complement the [[rfc:auto-capture-closure|Auto-capturing multi-statement closures]] RFC. Both stand on their own and offer independent benefits. However, they have been designed such that their syntax is complementary and consistent. Specifically: |
| |
| |
| * The ''=>'' sigil always means "evaluates to the expression on the right," in all circumstances. (Named functions, anonymous functions, arrays, and ''match()''.) |
| * ''{ ... }'' denotes a statement list, potentially ending in a ''return''. |
| * The ''function'' keyword indicates a function that has no auto-capture. |
| * The ''fn'' keyword indicates a function that will auto-capture variables, by value. |
| * A function with a name is declared globally at compile time. A function without a name is declared locally as a closure at runtime. |
| |
| These rules are easily recognizable and learnable by developers. |
| |
| (Further discussion of the possible permutations, and which are not useful to begin with, is in the Auto-capturing Closures RFC.) |
| |
==== Reasoning ==== | ==== Reasoning ==== |
| |
Many functions and methods are, in practice, simple expressions. They take input and return some simple output that can be computed in a single expression (of arbitrary complexity). When anonymous, short lambdas offer a compact way to express that function as a literal expression. Named functions, however, currently still require writing them as if they would be a long block of statements. That provides extra visual clutter (especially when there are several such methods in one class). It also forces you to code in "statement headspace" rather than "expression headspace". Allowing functions to be written in a more expression-y way helps with conceptualizing a program as evaluating expressions, not statement steps. | Many functions and methods are, in practice, simple expressions. They take input and return some simple output that can be computed in a single expression (of arbitrary complexity). When anonymous, short lambdas offer a compact way to express that function as a literal expression. Named functions, however, currently still require writing them as if they would be a long block of statements. That provides extra visual clutter (especially when there are several such methods in one class). It also forces you to code in "statement headspace" rather than "expression headspace." Allowing functions to be written in a more expression-y way helps with conceptualizing a program as evaluating expressions, not statement steps. |
| |
Expressions are becoming increasingly capable, too. match() expressions and throw expressions in PHP 8.0, plus proposals such as [[rfc:pipe-operator-v2|PHP RFC: Pipe Operator v2]], are collectively making it easier to write expressive expressions. This improvement is a part of that larger trend. | Expressions are becoming increasingly capable, too. match() expressions and throw expressions in PHP 8.0, plus proposals such as [[rfc:pipe-operator-v2|PHP RFC: Pipe Operator v2]], are collectively making it easier to write expressive expressions. Enumeration methods are likely to consist primarily of a single match() statement. This improvement is a part of that larger trend. |
| |
=== Pure functions === | === Pure functions === |
| |
function fullName(string $first, string $last): string | function fullName(string $first, string $last): string |
=> sprintf('%s %s %d', $first, $last, $GLOBALS['called']++; | => sprintf('%s %s %d', $first, $last, $GLOBALS['called']++); |
</code> | </code> |
| |
| |
print pick_one(1) . PHP_EOL; | print pick_one(1) . PHP_EOL; |
| </code> |
| |
| === Enum methods === |
| |
| In practice, most enum methods are likely to contain only a match expression, the evaluated value of which should be returned. That makes them a good candidate for short functions and eliminating visual clutter. |
| |
| <code php> |
| enum Suit |
| { |
| |
| case Hearts; |
| case Diamonds; |
| case Clubs; |
| case Spades; |
| |
| public function color(): string => match($this) { |
| static::Hearts, static::Diamonds => 'Red', |
| static::Clubs, static::Spades => 'Black', |
| }; |
| |
| vs: |
| |
| public function color(): string |
| { |
| return match($this) { |
| static::Hearts, static::Diamonds => 'Red', |
| static::Clubs, static::Spades => 'Black', |
| }; |
| } |
| } |
| |
</code> | </code> |
| |
function str_contains(string $haystack, string $needle): bool => strpos($haystack, $needle) !== false; | function str_contains(string $haystack, string $needle): bool => strpos($haystack, $needle) !== false; |
</code> | </code> |
| |
=== Piped functions === | |
| |
The pipe operator |> is still pending in an RFC, but the feedback on it before was generally positive. Short functions would allow a function to be easily defined as the composition of several other functions. | |
| |
<code php> | |
function doAThing(User $u) | |
{ | |
return $u |> 'step1' |> 'step2' |> 'step3' |> 'step4'; | |
} | |
</code> | |
| |
vs. | |
| |
<code php> | |
function doAThing(User $u) => $u | |
|> 'step1' | |
|> 'step2' | |
|> 'step3' | |
|> 'step4' | |
; | |
</code> | |
| |
Which is a really nice way to build up a pipeline through composition. (Modulo PHP's clumsy way of referencing functions by name, which is a separate matter being addressed elsewhere.) | |
| |
=== Conditional methods === | === Conditional methods === |
| |
That can collapse to this (a bit reordered): | That can collapse to this (a bit reordered): |
| |
| |
<code php> | <code php> |
The => operator has de facto become the "maps to this expression" operator: Short lambdas use it, match() uses it, array literals use it... It seemed the natural choice. Anything else would have been more confusing. | The => operator has de facto become the "maps to this expression" operator: Short lambdas use it, match() uses it, array literals use it... It seemed the natural choice. Anything else would have been more confusing. |
| |
The author favors using "function" as a keyword for all named functions rather than "fn", on the grounds that it would introduce additional visual clutter in classes that make use of both short and long functions, and increase the work when a short function needs to be modified into a long function. However, if the consensus is to use "fn" instead that is also possible. An alternate PR that does so, courtesy of Sara Golemon, is also available below. It also confines all its changes to the lexer so all the same arguments in its favor apply. | The use of the ''fn'' keyword was also considered, but rejected. In context, ''fn'' currently indicates that auto-capture will happen for variables from the lexical scope. (See the discussion above.) However, a named function has no meaningful values to capture, making ''function'' more appropriate. |
| |
As of this time, this RFC is to use "function" for short named functions. | ==== Related RFCs ==== |
| |
| A number of other RFCs in active consideration would complement short functions. They may or may not pass, but if they did then they would benefit from short functions without any further effort. |
| |
| === Piped functions === |
| |
| The [[rfc:pipe-operator-v2|pipe operator]] |> is still pending in an RFC, but the feedback on it before was generally positive. Short functions would allow a function to be easily defined as the composition of several other functions. |
| |
| <code php> |
| function doAThing(User $u) |
| { |
| return $u |> 'step1' |> 'step2' |> 'step3' |> 'step4'; |
| } |
| </code> |
| |
| vs. |
| |
| <code php> |
| function doAThing(User $u) => $u |
| |> 'step1' |
| |> 'step2' |
| |> 'step3' |
| |> 'step4' |
| ; |
| </code> |
| |
| Which is a really nice way to build up a pipeline through composition. (Modulo PHP's clumsy way of referencing functions by name, which is a separate matter being addressed elsewhere.) |
| |
| === clone-with === |
| |
| Although no formal RFC has been proposed, Máté had discussed a ''clone with'' syntax ([[https://github.com/php/php-src/pull/6538|code]], [[https://externals.io/message/112624|thread]]) that would create a useful, single-expression clone-with-modification operation. That is an excellent candidate for short-function "withX" methods. |
| |
| <code php> |
| class Point |
| { |
| public function __construct(private int $x, private int $y) {} |
| |
| |
| public function getX(): int => $this->x; |
| public function getY(): int => $this->y; |
| |
| public function withX($x): static => clone($this) with {x: $x}; |
| public function withY($y): static => clone($this) with {y: $y}; |
| } |
| </code> |
| |
| Thus making many cases of wither methods just as trivial to write as getter methods. |
| |
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== |
| |
PHP 8.1. | PHP 8.1. |
| |
| |
===== Open Issues ===== | ===== Open Issues ===== |
| |
[[https://github.com/php/php-src/pull/6221|Pull request with the code.]] | [[https://github.com/php/php-src/pull/6221|Pull request with the code.]] |
| |
[[https://github.com/php/php-src/pull/6379|Alternate PR that uses fn instead of function]] | |
| |
Pull request for the spec still to come. | |
| |
===== Implementation ===== | ===== Implementation ===== |