On Mon, Feb 24, 2020, at 3:25 PM, Mike Schinkel wrote: > > On Feb 23, 2020, at 12:06 PM, Larry Garfield <la...@garfieldtech.com> wrote: > > > > On Sun, Feb 23, 2020, at 2:39 AM, Mike Schinkel wrote: > >>> On Feb 21, 2020, at 6:17 PM, Larry Garfield <la...@garfieldtech.com> > >>> wrote: > >>> I'm totally on board for better value object support, so that's a good > >>> motive for me. The question I have is whether this is really a good > >>> stepping stone in that direction or if it would lead down a wrong path > >>> and lock us into too much TIMTOWTDI (for the Perl fans in the room). So > >>> let's think that through down that path. How would write-once properties > >>> lead into properly immutable value objects? Or do they give us that > >>> themselves? > >>> > >>> The biggest challenge for immutable objects, IMO, is evolving them. Eg, > >>> $result->withContentType(...) to use the PSR-7 example. Would we expect > >>> people to do it with a method like that, or would there be some other > >>> mechanism? If the properties are public, would we offer a more syntactic > >>> way to modify them directly? > >>> > >>> The with*() method style requires cloning the object. What happens to > >>> the locked status of a set property if the object is cloned? Are they > >>> then settable again, or do they come pre-locked? > >>> > >>> Neither of those seem good, now that I think about it. If they come > >>> pre-locked, then you really can't clone, change one property, and return > >>> the new one (as is the standard practice now in that case). If they > >>> don't come pre-locked, then the newly created object can have everything > >>> on it changed, once, which creates a loophole. I'm not sure what the > >>> right answer is here. > >>> > >>> My other concern is a public property (the most likely use case) would > >>> have to be set in the constructor. If it's not, then callers cannot rely > >>> on it having been set yet if it's set lazily. And if code inside the > >>> class tries to set it lazily, it may already have been set by some > >>> external code (rightly or wrongly) and cause a failure. > >> > >>> > >>> How do we address that? There's absolutely use cases where setting > >>> everything in the constructor ahead of time is what you'd do anyway, but > >>> there are plenty where you wouldn't want to, either, which creates a race > >>> condition for who sets it first, or tries to access it before it gets > >>> set, etc. (This is where my repeated questions about lazy initialization > >>> come from.) > >> > >> > >> I have struggled to follow this RFC thread fully, so if I am getting > >> something out of context, please note that and I apologize in advance. > >> > >> However, it would see that rules for `write once` properties to support > >> lazy loading would be rather simple: > >> > >> 1. Write-once properties can only be updated once. > >> 2. Write-once properties can only be updated within the class where > >> they are declared. > > > > This is the common use case I think many envision, but nothing in the > > proposal requires that. A public write-once property (as currently > > written) would be world-readable, and world-writeable, once. > > Separate visibility for internal and external access is a separate matter. > > (Also potentially useful, but not part of the write-once proposal at the > > moment.) > > This just hit me, so I think I will mention it. > > The culture on the list seems not to be one of collaboration on RFC to > find solutions to improve PHP but instead waiting for someone to stick > their neck out with an RFC and then see who can shoot the most holes > through it. > > I did not actually expect that. I would have hoped for a collaborate > culture of the nature I am used to in local startup-oriented meetups > where most everyone is trying to help each other rather than just > pointing out that the work someone else has done is deficient and not > worthy of moving forward.
Mike, Terje already responded here and he is spot on: "Destructive testing" is a very good metaphor to use, and is what is happening in this thread. Evolving a language like PHP, with millions of users and billions of lines of code, is not, even a little, like a startup-oriented meetup. In a meetup, "yes and" is a perfectly good model, and you can encourage everyone to make their own thing, throw it out into the market, and see what happens. Multiple incompatible and competing startups can succeed and the ecosystem is better for it. But there is only one PHP language, and throwing everything and the kitchen sink into it just to make people feel good and inspired is a superb way to ruin the language. *Every* new feature or syntax addition creates cognitive overhead for *millions* of people. If done in a way that has lots of holes or flaws, it can create billion-dollar bugs that take a decade to unravel, if ever. The cost of "getting it wrong" is high. The cost/benefit analysis has to be very strong to justify adding syntactic weight to every PHP user in the world. That does not mean that we should oppose all change; hell, PHP is perhaps the fastest evolving production language in the world right now. There's a long list of recent features I've supported and cheered for, and a long list I'd still like to see. The same is true of almost everyone on this list. But in order to keep PHP a good, approachable, flexible language and keep it consistent (well, at least no more inconsistent than it already is), every one of those features needs to "run the gauntlet" of destructive testing, poking holes in it, and finding all the gotchas. That's exactly the point: We *do* want to find all the little gotchas in a proposal, now, before something gets into the language and the PHP-coding world finds them instead. Constructive nitpicking and destructive testing, in this context, are a sign of respect. If folks on the list didn't respect this proposal, it would have been met with "this is stupid", "we went over this, it's dumb", "why are you wasting our time?", or other such responses. (Certainly there have been proposals met with such response in the past.) Or, simply ignored outright. Instead, it's being met with "Hm, someone is going to try and throw this against a wall harder than they have any right to; so let's do it now and see what happens so we can figure out how to make it not shatter into a thousand pieces." That's a good thing. Numerous people are donating a not-small amount of their time to stress-test the proposal. Some of that work has already improved the proposal, eg, the general agreement that it was best to just ignore untyped properties and references entirely. That's a good thing. Máté has said that his end goal is immutable objects, and this is a step in that direction. I fully support that goal. Is this actually the right first step in that direction, or does it go down a wrong path? I don't know yet; that's what all of the nitpicking and hypotheticals and "running it over with a car to see what happens" is about: Figuring out if it is the right way to get to that goal. I'm glad we're having this conversation. Maybe a readonly flag only makes sense if we first/also add separate visibility for getters and setters? I don't know, but it's worth asking. Does it break cloning? Seems like it, but maybe there's a way around it. Would some totally different approach get to a better end-state with fewer edge cases? Could be. Those are realizations that only happen when you take an RFC and "shoot the most holes through it", because then you find the places where it's not bulletproof. That's the process working as designed. > > 1) Race condition if I assume that a public write-once property is a > > materialized value, but access it before it gets materialized. > > Can you please explain what "materialized value" means in this context? > Are you speaking in Scala again? I don't speak Scala. If anything it's an SQL reference. A "materialized table" is basically a saved query result, which you can then reuse over and over again, that self-updates when its underlying source changes. Think $user->fullName, where fullName is a public read-only property derived by concatenating $firstName and $lastName. If that is set in the User constructor, great, no problem. If it's not, because it's not always needed so you want to only do that work if it's going to be used, then the caller doesn't know if it's already been set or if it's still undefined. Or, if public write isn't blanket-disallowed (which could be done but I suspect would lead to other limitations), whatever internal code would set the value doesn't know if it's already been set by an over-eager external routine (possibly incorrectly). > If you mean that a write-once property just has not yet been assigned, > I am not sure how that differs from just accessing a property that has > not yet been assigned today? It doesn't, which is one of the reasons public properties are broadly frowned upon today. However, most of the use cases of a readonly flag I can see apply to publicly-readable properties (which are therefore faster to access than a method and involve less boilerplate code). So if a readonly flag doesn't resolve that problem, and in practice makes it a bit worse, that puts it at a net-negative ROI. I keep coming back to supporting lazy-initialization because that seems to me the best way to resolve that problem, and gives us some very clean additional functionality. Are there other options that are better? Could well be. Let's enumerate them and poke holes in all of those, too. > > 3) Cloning creates an interesting and complicated case of both of the > > above. Does a cloned object start with its write-once bits reset or no? > > There's problems both ways. > > If there are problems, let us find solutions. > > 1. Only allow internal methods to change write-once properties. Possible; which then leads to the question "why can't every property have asymmetric access?" Which someone else upthread mentioned, and IMO warrants its own discussion. > 2. Options: > 2a. Extend clone statement to have `clone rewrite $foo` that would > allow rewriting the write-once properties. In the common case, you'd only want to overwrite a subset of properties, probably only one. How do you "unlock" just that one property? Is that the right way to go about it? (I honestly don't know.) > 2b. Allow rewriting once of write-once object properties until some > operation is performed to seal or lock the object (operation being a > method call, a function call with the object as param, or a statement > with the object as argument.) My preference here would be the "overwrite code block" Rowan suggested. That may have other knock-on effects (either for consistency or engine complexity) that we haven't thought of yet. > 2c. Don't allow rewriting of cloned objects. See if it is really a > problem. Fix it in a future RFC if so. Possible, but also then removes one of the primary use cases for the feature: Immutable value objects, since then evolving them becomes far more work than it is now. --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php