Why don't lazy parameters bind to delegates? Was: Feature to get or add value to an associative array.

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sat Apr 21 00:26:49 UTC 2018


On Friday, April 20, 2018 19:36:57 Steven Schveighoffer via Digitalmars-d 
wrote:
> On 4/20/18 5:40 PM, Jonathan M Davis wrote:
> > On Friday, April 20, 2018 16:35:43 Steven Schveighoffer via
> > Digitalmars-d
> >
> > wrote:
> >> On 4/17/18 4:49 PM, Steven Schveighoffer wrote:
> >>> On 4/17/18 12:18 PM, Nick Treleaven wrote:
> >>>> Thanks for making this pull, I've thought about solving this before.
> >>>> I
> >>>> think the function needs to provide a way to tell if the value was
> >>>> already present.
> >>>
> >>> Not as straightforward, but it can be done:
> >>>
> >>> bool inserted = false;
> >>> auto p = aa.getOrAdd("key", {inserted = true; return new Person; });
> >>
> >> Let me say I was surprised that this doesn't actually work. An in-line
> >> lambda will NOT work as a lazy parameter, even though that's EXACTLY
> >> what a lazy parameter is implemented as! And variadic lazy parameters
> >> are explicitly typed this way.
> >>
> >> Has that ever worked? I could have sworn it did...
> >
> > I'm not sure. I mucked around with lazy like this when I was originally
> > working on stuff like assertThrown and assertPred 7 or 8 years ago, but
> > I've done _very_ little with lazy since then. My gut reaction is that
> > it worked, but it might not have. If you add parens after it so that
> > you call it, it does work, since then the result is the correct type.
> > And if you ignore the exact details of how lazy is implemented and
> > consider that it's supposed to take an expression that evaluates to a
> > specific type, a lambda doesn't match that unless it's called. So, it
> > does make sense from that perspective and is likely why it works the
> > way it does.
>
> But, it *is* a delegate. literally, that's what gets implemented (and it
> has to be that way). I suppose if it's inlined, the compiler could take
> some leeway, but that is the same with a delegate literal passed to a
> function that takes a delegate anyway.

If we want to make the compiler accept it, then I'm not necessarily against
it, but arguably, the fact that it uses a delegate is an implementation
detail. In principle, what you're doing with a lazy parameter is just saying
that the expression being passed to it isn't immediately evaluated. Having
it then accept expressions that don't result in the type of the lazy
parameter is then pretty weird.

> The fact that lazy variadics are explicitly delegates makes this even
> more annoying. I wish we could have the best of both worlds (non-ugly
> calls inside the function, and easy binding to whatever you want at the
> call side). I'm not even sure why lazy variadics are the way they are, I
> can instantly think of a better syntax for them.

Well, this thread is the first that I've heard of "lazy variadics." Until I
saw your other post about them, I assumed that you meant something like

auto foo(Args...)(lazy Args args)
{
    ...
}

I would haven't have assumed that

void foo(int delegate()[] dgs...)
{
    dgs[0]();
}

had anything to do with lazy, and the fact that it works with anything that
doesn't implictly convert to a delegate seems like a bug to me, especially
when

void foo(int delegate() dg)
{
    dg();
}

doesn't compile when it's passed an int. IMHO, they should be consistent.

> > The compiler would basically have to
> > special-case delegates to accept stuff like lambdas when the type of the
> > lazy parameter is not a delegate. And if it _did_ special-case it, then
> > things might get interesting if you actually had a lazy parameter that
> > was a delegate.
>
> This is pretty easy, does your delegate return a delegate or an int? ;)
>
> This is a super-solvable problem, I'm just surprised it wasn't this way
> before, I could have sworn I've done this in the past.

Of course it's solvable, but that doesn't mean that it's ultimately a good
idea. It does have corner cases that may or may not be a problem. e.g.
something like

void foo(lazy int delegate() dg)
{
}

could be a problem if

void foo(lazy int i)
{
}

accepts delegates whose result is an int. I'm not necessarily against making
it possible, but I would consider the fact that lazy is implemented via a
delegate to be an implementation detail of lazy, and if lazy parameters
started accepting lambdas whose result was the right type, then that means
that

auto foo(string str) {...}

and

auto foo(lazy string str) {...}

would accept different arguments even though their parameters are the same
type, and I question that that's a good idea. I'm sure that it would work on
some level, and the corner cases probably wouldn't matter often, but it
seems much cleaner to me if lazy has no effect on what is accepted and just
has an effect on whether that code is run immediately.

> > So, I don't know if it should work or not, but the workaround is
> > pretty simple - just add parens to call it.
>
> This results in a double delegate call -- the compiler makes a delegate
> that calls your delegate. Not optimal, but yes it does work.

Sure, it's sub-optimal, and I would hope that the compiler would optimize it
accordingly (though it wouldn't surprise me if it doesn't), but it works
without having to change anything, and it would presumably continue to work
even if we did change the behavior of lazy to accept lambdas whose return
type was the type of the parameter.

- Jonathan M Davis



More information about the Digitalmars-d mailing list