> On Aug 26, 2024, at 8:28 AM, Rowan Tommins [IMSoP] <[email protected]> wrote:
>
> On Mon, 26 Aug 2024, at 11:43, Mike Schinkel wrote:
>>> You ask how a library can provide access to that default, and the answer is generally
>>> pretty trivial: define a public constant, and refer to it in the parameter definition.
>>
>> A global? Really?
>
> I didn't say "global", I said "public".
I was imprecise in my description. I meant "global" in the general programming sense and
not in the PHP keyword global
sense.
Sorry about my lack of precision here.
> Since you're keen on real-world examples, here's a simplified version of a real
> class:
>
> class ConfigSource
> {
> private HttpClientInterface $httpClient;
>
> public const DEFAULT_CONSUL_URL = 'http://localhost:8500';
>
> public function __construct(private string $consulUrl=self::DEFAULT_CONSUL_URL,
> ?HttpClientInterface $httpClient=null)
> {
> if ( $httpClient === null ) {
> $httpClient = new HttpClient();
> $httpClient->setRequestTimeoutSecs(5);
> $httpClient->setConnectRequestTimeoutSecs(5);
> }
> $this->httpClient = $httpClient;
> }
> }
>
> This constructor has two optional parameters; one of them uses a default which is also
> referenced explicitly in another class, so is exposed as a constant; the other uses a completely
> opaque method of creating a default object, which even Reflection could not expose.
>
> The caller doesn't need to know how any of this works to make use of the class. The
> contract is "__construct(optional string $consulUrl, optional ?HttpClientInterface
> $httpClient)"
>
> The purpose of the optional parameters is so that you can write "$configSource = new
> ConfigSource();" and trust the library to provide you a sensible default behaviour.
Thank you (sincerely) for taking the time to author a real-world use-case.
> If it was decided that the code for creating a default HttpClient was needed elsewhere, it
> could be refactored into a method, with appropriate access:
>
> public function __construct(private string $consulUrl=self::DEFAULT_CONSUL_URL,
> ?HttpClientInterface $httpClient=null)
> {
> $this->httpClient = $httpClient ?? $this->getDefaultHttpClient();
> }
>
> public function getDefaultHttpClient(): HttpClient
> {
> $httpClient = new HttpClient();
> $httpClient->setRequestTimeoutSecs(5);
> $httpClient->setConnectRequestTimeoutSecs(5);
> return $httpClient;
> }
So, nullable is an equivalent to the union-type concern my discussion with John Coggeshall
uncovered, correct?
When two different types can be passed then we cannot depend on the default
being a
specific type.
> Or perhaps the HttpClient becomes nullable internally:
>
> public function __construct(private string $consulUrl=self::DEFAULT_CONSUL_URL, private
> ?HttpClientInterface $httpClient=null) {}
>
> Or maybe we allow explicit nulls, but default to a simple class instantiation:
>
> public function __construct(private string $consulUrl=self::DEFAULT_CONSUL_URL, private
> ?HttpClientInterface $httpClient=new HttpClient) {}
>
> None of these are, currently, breaking changes - the contract remains
> "__construct(optional string $consulUrl, optional ?HttpClientInterface $httpClient)".
Great example.
Now consider the following, assuming the RFC passed but with an added requirement that
match()
be used exhaustively for nullable parameters (shown) or parameters type hinted
with unions (not shown):
$configSource = new ConfigSource(default,match(default){
NULL=>new ConfigSource->getDefaultHttpClient()->withRequestTimeoutSecs(5),
default=>default->withRequestTimeoutSecs(5)
})
Ignoring that this expression is overly complex, if covering all "types" would be a
requirement in the RFC — where NULL
is a type here for purposes of this analysis
—can you identify another breaking change if the RFC passed?
> On Aug 26, 2024, at 8:43 AM, Rowan Tommins [IMSoP] <[email protected]> wrote:
> On Mon, 26 Aug 2024, at 11:43, Mike Schinkel wrote:
>>
>> But saying that we can be certain default values are private and we
>> want to keep them that way is provably false by the existence of
>> Reflection.
> Right now, accessing the default value of an optional parameter is in the "possible if you
> mess around with Reflection APIs" set; maybe we do want to move it to the "is part of the
> language" set, maybe we don't; but claiming there is no distinction is nonsense.
Now don't go putting words in my mouth, Rowan. ;-)
I did not say there was _no_ distinction, I said that we cannot be *certain* the values are indeed
private. Subtle difference I admit, but there _is_ a difference. :-)
> On Aug 26, 2024, at 12:59 PM, Larry Garfield <[email protected]> wrote:
> The problem here is that foo(default | ADDITIONAL_FLAG)
is so far the most
> compelling use case I've seen for this feature. It's really one of only two I see myself
> possibly using, frankly...
>
> The other compelling case would be the rare occasions where today you'd do something like
> this:
> <snip>
> foo($beep, $user_provided_value ?: default);
Is foo((default)->WithLogger(new Logger));
not a compelling use-case — which
illustrates the clone of an default initialized object with injected dependencies with one or a few
properties then modified — or did you just miss that use-case in the volume of this thread?
> On Aug 26, 2024, at 1:35 PM, John Coggeshall <[email protected]> wrote:
> From the example you gave it appears that we can have a concrete problem when:
> 1. There is a parameter with a default value,
> 2. That parameter is type-hinted,
> 3. The hinted type is declared as a union type,
> 4. An earlier version of the library initialized the default with a value having one of the
> union types,
> 5. End-user developers used the library and then use default
as an expression of
> that type, and finally
> 6. The library developer changed the initialization of the default
to a different
> type from the union.
> Did I correctly identify the problematic use-case?
> Not really. #2 and #3 are irrelevant mixed is actually much more problematic, I wanted to
> provide an example that was strongly typed intentionally to show the problem even when types were
> explicit. The relevant portion is #1, #5 and #6.
For the purpose of this analysis mixed
can be considered just a special case of a
union-type where the types available are "all."
You say #2 and #3 are irrelevant and mention mixed
, but if we consider
mixed
as a special type of union then #2 and #3 become relevant again (as well as
nullable, as Rowan uncovered, which is itself a special type of union.)
Or maybe there are problems with single types that we have not uncovered yet?
> Ok, so for argument sake, what if they revise the RFC to only allow default
to be
> used in an expression when the parameter is not type-hinted with a union? Would that address your
> concern? Or are there other use-cases that are problematic that do not hinge on the parameter being
> type-hinted as a union type?
>
> It wouldn't be enough, offhand it'd also have to be forbidden for mixed
Okay — with the comment below considered — then assuming the RFC were to disallow using
default
as an expression when the parameter were type-hinted with mixed
(and when no type hint is used), what are breaking changes we still need to worry about?
Note default
could still be used with mixed
, but only by itself, not in an
expression.
> (at which point I think the utility isn't there anymore).
I think this argument is provably false by the fact that — minimally — the use-case that has
resonated with several people who are otherwise lukewarm has been bitmapped flags, for which type
hinting as mixed
would be an obvious code smell.
But even so, it is up to the RFC author to decide if those limitations would diminish the utility
too much to continue the RFC, and then for the voters to decide if they agreed with the remaining
utility.
> On Aug 26, 2024, at 2:11 PM, Matthew Weier O'Phinney <[email protected]>
> wrote:
> There's been a few good lists about the cool things this could enable, demonstrating the
> value; maybe now we should focus on the "we absolutely shouldn't enable" pieces to
> allow for broader consensus.
+1!
> On Aug 26, 2024, at 2:49 PM, John Coggeshall <[email protected]> wrote:
> Perhaps the answer could be to only allow the use of default when the assigned default value
> is a scalar value -- no objects, arrays, enums, etc (and no mixed )..
FWIW, that would eliminate many of the use-cases for me where I see the feature has value.
> On Aug 26, 2024, at 3:12 PM, Bilge <[email protected]> wrote:
> Whilst this is a curiosity, consider that passing match expressions directly to arguments is
> something I personally have never witnessed and that goes doubly for combining it with
> default
. So, whilst it is interesting to know, and important for the RFC to state the
> specific semantics of this scenario, the practical applications are presumed slim to none
>
Eh, see nullable and union types (mentioned above.) :-)
-Mike
P.S. The more I think about this, the more I think that the default value *should* be a formal part
of the function signature. The fact that has not been explicitly defined before now — due to the
fact it wasn't relevant before this RFC — does not automatically require that it not be a
formal part of the function signature, that is just the implicit status quo. This is likely
something this RFC or a precursor RFC should ask voters to vote on explicitly, and then it would be
decided.