rfc:default_expression

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:default_expression [2024/08/27 08:40] bilgerfc:default_expression [2024/08/29 21:46] (current) – Added two seconary discussion concerns bilge
Line 11: Line 11:
 ===== Introduction ===== ===== Introduction =====
  
-The only way to pass the default value to a function or method parameter is to not pass anything. This can be particularly difficult in some circumstances, because the language does not offer an intuitive way to pass //nothing//. This RFC proposes to make the existing keyword, <php>default</php>, the canonical way to pass the default value. Moreover, ''default'' becomes a valid expression in the function argument context, meaning it can be creatively combined with any and all existing PHP expression grammars to augment the default value as it is passed, including assigning it to variables (see [[#appendix_iidefault_expressions|appendix II]] for a comprehensive list).+The only way to pass the default value to a function or method parameter is to not pass anything. This can be particularly difficult in some circumstances, because the language does not offer an intuitive way to pass //nothing//. This RFC proposes to make the existing keyword, <php>default</php>, the canonical way to pass the default value. Moreover, <php>default</php> becomes a valid expression in the function argument context, meaning it can be creatively combined with any and all existing PHP expression grammars to augment the default value as it is passed, including assigning it to variables (see [[#appendix_iidefault_expressions|appendix II]] for a comprehensive list).
  
 In its simplest form, <php>default</php> can be used as a single token. In its simplest form, <php>default</php> can be used as a single token.
Line 103: Line 103:
 <code php> <code php>
 $f = fn ($v = 1, $default = 2) => $v + $default; $f = fn ($v = 1, $default = 2) => $v + $default;
-var_dump($f(default: default + 1)); // int(4)+$f(default: default + 1); // int(4)
 </code> </code>
  
Line 120: Line 120:
 In the above example, the match expression and the match body (right of <php>=></php>) are the default //expression//, while the default in the match condition (left of <php>=></php>) is the special token denoting the default match arm to use when no other arm matches, the same as it was before this RFC. However, it is also possible to use the default expression as a condition, simply by combining it with any other expression syntax. In the above example, the match expression and the match body (right of <php>=></php>) are the default //expression//, while the default in the match condition (left of <php>=></php>) is the special token denoting the default match arm to use when no other arm matches, the same as it was before this RFC. However, it is also possible to use the default expression as a condition, simply by combining it with any other expression syntax.
  
-Although there is no identity expression that works for all types, if you know the type of ''default'', casting it to the same type is one way to convert it to an expression.+Although there is no identity expression that works for all types, if you know the type of <php>default</php>, casting it to the same type is one way to convert it to an expression.
  
 <code php> <code php>
Line 138: Line 138:
  
 Currently the only known failure case is lookup of trampoline functions, which can be created by calling <php>__invoke</php> on a closure, as in <php>(fn ($P = 1) => $P)->__invoke(default);</php>. Considering this is not the intended, nor even a documented way of invoking a closure, it is supposed this limitation is very minor. Currently the only known failure case is lookup of trampoline functions, which can be created by calling <php>__invoke</php> on a closure, as in <php>(fn ($P = 1) => $P)->__invoke(default);</php>. Considering this is not the intended, nor even a documented way of invoking a closure, it is supposed this limitation is very minor.
 +
 +===== Discussion =====
 +
 +The greatest concern is the proposed grammar is too permissive and has drawbacks. Secondary concerns include evaluating <php>default</php> in the calling context, and default values are now part of an object's public API. We will examine each of these issues in detail.
 +
 +==== Limiting grammar ====
 +
 +The most common request is to constrain the allowed expression list. As already noted, some expressions don't make much sense because they probably don't have any practical application, and some are not comfortable allowing expressions that don't make sense into the language. This implies coming up with an [[https://externals.io/message/125183#125218|arbitrary exclusion list]] for certain expressions. Some proposed taking this a step further by [[https://externals.io/message/125183#125321|disallowing default as expression input]], which effectively rules out all operator classes except conditionals and invocations of <php>match()</php> that use <php>default</php> as a stand-alone output token.
 +
 +<code php>
 +// Expressions with default as output only.
 +F(1 ? default : 0)
 +F(1 ? 1 : default)
 +F(0 ?: default)
 +F(null ?? default)
 +F(match(1) { 1 => default })
 +</code>
 +
 +Further, some even expressed concerns about allowing any expressions at all and would only be comfortable allowing <php>default</php> as an isolated token, as in [[skipparams]].
 +
 +Critics converged on a [[https://externals.io/message/125183#125274|valid counter-point]] that permitting expressions changing the default's type breaks LSP, as demonstrated in the following example (code courtesy of Ilija). Some have suggested this might be solved by disallowing <php>default</php> to be passed to union types (including <php>mixed</php>).
 +
 +<code php>
 +class C {
 +    public function F(int $V = 1) {}
 +}
 +
 +class D extends C {
 +    public function F(int|string $V = 's') {}
 +}
 +
 +function test(C $C) {
 +    $C->F(default + 1);
 +}
 +
 +test(new C); // OK.
 +test(new D); // Fatal error: Uncaught TypeError: Unsupported operand types: string + int.
 +</code>
 +
 +==== Default as a dummy value ====
 +
 +Currently <php>default</php>, as described by this RFC, is effectively replaced by the callee's default value and then passed to the callee from the caller, meaning the caller has full access to the default value. Some have [[https://externals.io/message/125183#125265|argued]] for an implementation that more literally follows the premise of this RFC, which is that <php>default</php> is just a dumb token that is standing in for //nothing//; it does not represent any value to the caller and merely instructs the callee to use its default value in the same way as when not passing the argument.
 +
 +==== Defaults as a contract ====
 +
 +Some have argued allowing <php>default</php> to read argument default values, previously only accessible via reflection, suddenly makes defaults part of an object's published API. However, changing a default is a behavioural change for any caller previously relying on those defaults (by not passing any argument), ergo defaults have always been part of the published API.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
Line 155: Line 201:
    * No    * No
 </doodle> </doodle>
 +
 +Regardless of how you vote above, we'd like to collect feedback on which limitations of this proposal would make it/still be acceptable for you, starting from the least significant to the most significant changes. For details on what each of these options mean, see [[#discussion|discussion]]. You can vote for multiple options, but if you choose the last one then it doesn't make much sense to pick any others.
 +
 +<doodle title="Which limited grammars would you support?" auth="bilge" voteType="multi" closed="false" closeon="2024-08-09T21:00:00Z">
 +   * No union types
 +   * Only conditional expressions
 +   * No expressions
 +   * No default in arguments
 +</doodle>
 +
 +Only the result of the primary vote has a clear path forward for inclusion into the language. None of the proposed alternatives come with any feasibility guarantees. However, if the primary vote fails, the secondary vote may inform whomsoever wishes to pursue a follow-up for limited application of ''default'', which requires a new RFC and implementation for that counter-proposal.
  
 ===== Appendix I: Further examples ===== ===== Appendix I: Further examples =====
Line 248: Line 305:
 // Match // Match
 F(match(default) { default => default }) F(match(default) { default => default })
 +
 +// Callable
 +F((default)->M())
  
 // Parens // Parens
Line 265: Line 325:
 F(print default) F(print default)
 </code> </code>
- 
-===== Discussion ===== 
- 
- 
  
 ===== References ===== ===== References =====
rfc/default_expression.1724748025.txt.gz · Last modified: 2024/08/27 08:40 by bilge