On Mon, Jul 29, 2019 at 5:35 PM Rowan Collins <rowan.coll...@gmail.com> wrote:
> On Mon, 29 Jul 2019 at 15:41, Nikita Popov <nikita....@gmail.com> wrote: > > > This proposal (at least combined with a declare that enforces use of > > call-site annotations) addresses that concern. out/inout parameters do > not > > address this concern, because they are an mechanism that would be used > *in > > addition* to normal by-ref passing, and as such not address any problems > it > > has. > > > > The only way I see in which out/inout would actually address the concern > > of this proposal is if out/inout were used instead of & at the call-site, > > but the parameter were still a normal reference. I don't think that's a > > good idea because it breaks symmetry between arguments and parameters, > and > > also squanders any future potential to use out/inout as a way to reduce > > reference-use in this context. > > > > Does that make sense, Rowan? To put is more compactly, what I want is an > > eventual state where given $fn($a) I have a guarantee that $a is not > going > > to be magically modified, without having to perform any global reasoning > > about what $fn may refer to. If an alternative proposal does not result > in > > this eventual state, then it is not an alternative to this proposal (but > > possibly still a valuable addition in itself). > > > > > It does, yes. I guess what I had in mind was that *in the long-term*, > out/inout parameters would replace & parameters, so rather than > declare(require_reference_at_call_site=1) you would have > declare(disable_reference_parameters=1). > Something like that could in principle address the problem, though it would enforce certain design decisions to allow reaching that eventual state. I think this is how out/inout would have to work in order to satisfy BC concerns while still allowing a declare-based mode that eliminates plain by-ref passing entirely: * All current by-ref parameters in extensions must be changed to out/inout parameters. * By default, an out/inout parameter will behave exactly like a reference parameter and there is no behavioral difference between out/inout. * Additionally, the call-site is allowed to specify out/inout. If it is specified, it must match the parameter. If it is specified, inout will generate a notice if the passed value doesn't exist (while out behaves as usual reference pass). * A declare() exists that would require require call-site out/inout annotations and prohibit calls with reference parameters (that have not been converted to out/inout). * Additional changes to type-checking for out/inout parameters in userland functions. One migration problem that would still exist is that 3rd-party libraries would also have to migrate to out/inout parameters first, before users could enable the declare() mode that disables reference-passing -- otherwise they would not be able to make use of some of the library functionality, or would have to opt-out in parts of the code. Alternatively one could allow the use of out/inout with normal reference parameters as well, in which case both out/inout would be allowed at the call-site. Whether or not it's a direct alternative to this proposal, I also worry > that out/inout parameters are somewhat mutually exclusive with it: the more > effort is spent on making reference parameters "better", the less appetite > there will be for replacing them altogether. This kind of goes both ways: There is such a thing as "good enough" and there's also the typical effect that going from a 95% solution to a 99% solution will often take many times more effort (what's the technical term for this?) In this context, "effort" isn't even the right word, it's a combination of initial implementation effort, long-term maintenance cost and possibly most importantly, language semantic complexity. Above, I have outlined a way in which out/inout could work while mostly addressing my original motivation. But is this approach really better than a simple call-site "&" annotation that flawlessly integrates into the existing reference passing system? I'm not convinced that it is. Especially if we need to introduce out/inout in a way that interoperates well with references for BC/migration purposes, the semantics become fairly complex, while (imho) not offering a lot of benefit over a simple by-reference pass. The main benefit I see would be an opportunity to fix the current interaction of references and type annotations. Every time I have discussed out/inout with people before, it has been in the context of "What would we have to do to completely remove references?" A large part of that is finding an alternative to by-reference passing that does not use references. Hack introduced inout parameters (see https://hhvm.com/blog/2018/03/15/hhvm-3.25.html) specifically to avoid the issues of reference-passing (and they behave essentially like the desugaring I mentioned in some earlier mail). But we would not get these benefits using the approach outlined above, and I don't believe we could use something significantly different while still supporting existing by-ref internal functions. This is why I don't think these two things should be mixed. Proper out/inout parameters should not have anything to do with references. I agree the behaviour of type > annotations would need deciding, but arguably that should have been true > for by-reference parameters; it's somewhat bizarre that you can write > "function foo(SomeClass &$x) { $x=42; }" The solution would probably be to > reuse the "typed references" from the typed property implementation. > > > > > > Bob has brought up another interesting issue: This proposal currently > does > > not address the case of foo(...$a), where references into $a may be added > > if foo() has by-reference parameters. The suggestion was to use > foo(&...$a) > > -- however in this case only to *allow* the use of references, not > require > > it (some args may be by-val while others may be by-ref). > > > > > There's probably a bit of a rabbit-hole if the mission is "make references > explicit". An array containing a reference can be passed by value, for > instance, so $fn($a) might not technically modify $a, but still modify > elements inside it. So it seems like we come back to "this is kind of > useful information but may not actually offer a hard guarantee". > Right, references in arrays and objects are a pretty big WTF factor (especially in how they behave with copying an cloning), but I don't really have a good answer to those. Regards, Nikita