> On Aug 26, 2024, at 8:28 AM, Rowan Tommins [IMSoP] <imsop....@rwec.co.uk> 
> 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] <imsop....@rwec.co.uk> 
> 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 <la...@garfieldtech.com> 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 <j...@coggeshall.org> 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 
> <mweierophin...@gmail.com> 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 <j...@coggeshall.org> 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 <bi...@scriptfusion.com> 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.

Reply via email to