> 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