> On Aug 26, 2024, at 2:26 AM, John Coggeshall <j...@coggeshall.org> wrote:
> The proposal in the RFC creates a new dependency and backward compatibility 
> issue for API developers that currently does not exist. It is not just 
> because it allows for non-sensical expressions, but that it allows perfectly 
> sensical expressions that would create dependencies between libraries that I 
> don't think are a worthwhile tradeoff. See my code example below.

If you reread my email you'll note I divided it into two objections and your 
reply seems not to recognize that. 

Your comment defends against the objection I listed as #2 but you quoted the 
discussion of the objective listed as #1. 

> (TL;DR; Down the thread a bit I put together a concrete example of why I'm 
> opposed to this RFC)

I read through the entire thread and I did not see any examples you provided 
that provided any real-world concerns, unless I misunderstood. But since you 
provided a better example let us just ignore my comments on those.

> Consider this metaphor -- If I have a object with private properties, PHP 
> doesn't allow me to reach into that object and extract those values without a 
> lot of work (e.g. Reflection) by design. Right now, there are lots of 
> libraries out there defining default values and right now today they are in 
> the same sense "private" to the function/method they are defined for. This PR 
> would change the visibility of those default values, pulling them higher up 
> into the call stack where IMO they don't belong -- essentially making them a 
> brand new dependency API devs need to worry about.

I acknowledged that. However, I was not convinced that the default value 
concern is an actual concern vs. a theoretical concern.  So that is why I asked 
for specific examples where it would cause a real problem vs. just a 
theoretical concern. 

> Sure if I have `foo(int $index=1)`, a developer calls with `foo(default*3)`, 
> and then the author of foo changes the signature to `foo(int $index=0)` that 
> might cause problems, but what is a real world scenario where a developer 
> would actually do that, the author then change it, and then is causes a 
> non-trivial problem?
> 
> How is that not a real-world contrived scenario?

Because there was no use-case described for:

1. The purpose of the function,
2. Why someone would multiply the default times three, nor
3. Why the API developer would break BC and change the default for the 
use-case. 

That kind of information is what I was looking for.

Besides, are you really calling a function named "foo()" a "real-world 
scenario?"  ;-)

Anyway, I am going to skip to your example because otherwise I would just be 
repeating my call for concrete examples in response to your earlier comments in 
that reply.

> Let me propose this example and see if you still hold firm to your option 
> that the following expression would not be valid and that it still would not 
> be a good idea for the language:
> class MyDependency {...}
> function doSomething(MyDependency $dep= new MyDependency) {...}
> doSomething((default)->WithLogger(new Logger));
> 
> Let's make that a little more complicated so you'll see the problem -- 
> Consider this rather lengthy example building off the concept of your example:
> 
> <snip>
> 
> (new A)->withLogger((default)->log('B'));

Well, this is not the example I asked you to comment on. Yes, you are using 
`WithLogger()` but you are not using it in the same context I asked about. 

Nonetheless, I will consider your example. It would be nice if you would 
revisit mine.

> This would be valid under this RFC, right?

No, because the method log() is implicitly void.  WithLogger() expects a 
Logger, not a null. But then I expect your example just had some logic errors, 
no?

BTW, in your example I am not sure I understand what `log()` does. I assume it 
is logging a value?

> But now as the author of class A I later want to change the default of my 
> withLogger  method. Instead of just passing in new DatabaseLogger, I now want 
> to change my API default to just a LoggerType::DB enum for reasons (What the 
> reasons aren't relevant).
> 
> Today I don't have to think too hard about that change from an API BC 
> perspective because the consumer has either passed in a LoggerInterface  or a 
> LoggerType  -- or left it empty and used it's default value. I can just 
> change the default to LoggerType::DB  and be on my way. The downstream 
> developer will never know or care that I did it because if they wanted to 
> call log()  they had to first create their own instance of LoggerInterface  
> and have a reference to that object in their local context like so:
> 
> $logger = LoggerType::DB->getLogger();
> (new A)->withLogger($logger);
> $logger->log('B');
> 
> With this RFC, now I can't  change this API call without introducing a BC 
> break in my library because I have no idea at this point if some downstream 
> caller decided to use my default value directly or not.

Okay, this I can work with. Thank you.

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?

Let us assume I did. Clearly this would be a BC break and the type you are 
concerned with.

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? 

Note they could also propose a default value be able to be set for each type in 
a union, with the first one being the default default if all else is equal. 
That might not be exactly to your liking, but it seems like it could address 
your stated problematic use-case.

BTW, you could also guard against that problem you claim you cannot guard 
against by ensuring your parameters with defaults are single types with no 
extraneous methods. In your example if you instead used a LoggerGetter 
interface with a single method GetLogger() you would sidestep the problem you 
are concerned with completely, and you might end up with a better API, too.:

public function withLogger(LoggerGetterInterface $a = new 
DatabaseLoggerGetter): static
{
    $this->log = $a->getLogger();
    return $this;      
}


> You can argue if this is a good API design or not, but it was only written to 
> provide a real example of how pulling the default value higher up the call 
> chain and allowing it to be used in expressions is problematic for library 
> authors all to save a couple of lines of code on the consumer side.

FWIW, I do not see the RFCs benefit as "saving lines of code" so much as 
instead "improving clarity of code."

> I'm honestly not sure what you're asking here. PHP currently doesn't allow 
> you access to the "default value" of a function you are calling (maybe 
> Reflection? I don't know offhand).

You wrote (paraphrasing) "Developers shouldn't be allowed to access defaults 
because they API did not explicitly allow them to."  Well then, it is simple 
completion of the binary logic to ask "How then do they explicitly give 
permission?"

IOW, if the argument is it can't be accessed because of lack of explicit 
permission it seems maybe what is needed is an RFC to decide:

1. Should defaults just be assumed to be part of the public API, and 
2. If no, then how can defaults be explicitly made public?  

Maybe this?

function withLogger(Logger $a = public new Logger): Logger {...}

> So yes, I am pointing out a problem but not providing a solution because I 
> don't currently agree a solution is even needed.

Fair enough.

OTOH, depending on the number who support this (I have no idea the number) you 
might be in a position that you'll get what you don't want unless you can 
propose a solution that addresses your concerns  while also meeting their needs 
to. Just something to consider.

> On Aug 26, 2024, at 3:28 AM, Rowan Tommins [IMSoP] <imsop....@rwec.co.uk> 
> wrote:
> I was responding to someone justifying anything and everything the proposal 
> allows, because Reflection already allows it. If the feature was "first class 
> syntax to access private methods of a class", I don't think it would be 
> controversial to challenge it. Saying "Reflection can already do it" would be 
> a poor defence, because part of Reflection's job is to break the normal rules 
> of the language.

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.  

Yes, I get that it is more likely people would use this `default` keyword in 
this way that they would use Reflection but not permitting default as 
expression that does not provide any more _guarantee_ they won't then they 
already have.

> The overriding rule, in my head, is that the caller shouldn't need to know, 
> and shouldn't be able to find out, what a particular implementation has 
> chosen as the default. So the particularly problematic operations are things 
> like this: 
> 
> foo($whatWasTheDefault=default) 
> foo(logAndReturn(default))
> foo(doSomethingUnrelated(default) && false?: default) 
> 
> I can see people inventing use cases for these as soon as they're available, 
> but by doing so they will be completely changing the meaning of default 
> parameters, which currently mean "I trust the implementation to substitute 
> something valid here, and have no interest in what it is".

Yes, that is the way it has (mostly) been, but justifying your argument that 
(you believe) the caller shouldn't know the default's value simply because it 
is currently not (generally) accessible is a poor defense for why it should not 
be accessible. Or as they say; live by the sword, die by the sword.

It is unrealistic to say that a developer shouldn't know anything about what a 
default value is because — without knowing what what the default actually is — 
how does a developer know the default value is the proper value for their 
use-case?

Given that, I think there is an argument to be made that default values 
*should* be made public and accessible and that developers who write functions 
should plan accordingly.  (I am not 100% sure I buy the argument I just made 
yet, but I am also not sure that I do not.). Making it part of the signature 
would make even more behavior explicit, and in most cases that is a good thing.

> Note that under inheritance, the default value may even change type:
> 
> class A { public function foo(int $bar=42) { ... } }
> class B extends A { public function foo(int|string $bar='hello') { ... } }

*Arguably*, that could violate LSP.  But even if not, it could violate the 
principle of least astonishment.

OTOH, you are still presenting abstract hypotheticals with no evidence that 
there exists use-case where developers would actually do what you are worried 
about them doing. How about providing a concrete example use-case as John 
Coggeshall did? Note we discovered a specific problematic use-case that might 
be avoided or worked-around by our collaborating in that way.

> 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?

Yes, you can namespace them, but that doesn't tie them to the function or the 
class. Too bad. 

> The only exception I think is "new in initializer", where you would have to 
> provide a function/method instead of a constant, and couldn't currently reuse 
> it in the actual signature.

Besides that Mrs Lincoln, how was the play?

Oops, sorry. I forgot you were not an American. ;-)

> Aside: one of those examples brings up an interesting question: is the value 
> pulled out by "default" calculated only once, or each time it's mentioned? In 
> other words, would this create 3 pointers to the same object, or 3 different 
> objects? 
> 
> foo(array|Something $x=new Something);
> foo([default, default, default]);

That is a question for the RFC author.

> On Aug 26, 2024, at 5:03 AM, Andreas Leathley <a.leath...@gmx.net> wrote:
> When I have the GzipCompression class, I would know there is a default
> value for $level, but when using the interface there might or might not
> be a default value, depending on the implementation. As far as I read
> the RFC, using "default" when there is no default would lead to a
> runtime exception, but there is no way of finding out if there is a
> default if you do not already know. Being able to test that could be
> useful, although I am not sure about the syntax for that. 

Great catch!

> On Aug 26, 2024, at 6:32 AM, Andreas Heigl <andr...@heigl.org> wrote:
> I think I am missing something here. From my understanding we are *either* 
> coding against the interface and then it should not be possible to use 
> `default` at all as no default is set in the interface. So the fatal error is 
> totally valid for me.

Maybe default value for method parameters in interfaces is something the RFC 
should consider enabling? 

-Mike

Reply via email to