April 27, 2016
On 4/27/16 1:04 PM, Andrei Alexandrescu wrote:
> On 04/27/2016 11:44 AM, Steven Schveighoffer wrote:
>> On 4/27/16 8:31 AM, Andrei Alexandrescu wrote:
>>> On 04/26/2016 03:45 PM, Jack Stouffer wrote:
>>>> I think that the drawback you mentioned does not outweigh the benefits
>>>> gained from using actual lambdas.
>>>
>>> Actually it turns out to be a major usability issue. -- Andrei
>>
>> Yes, consider that RedBlackTree!(int, (a, b) => a > b) is going to be a
>> different type every time you use it, even if they are 1 line apart!
>>
>> There are actually 2 things the string lambdas have going for them: 1)
>> common instantiation for every usage, and 2) avoiding parentheses with
>> instantiation (impossible to do with a short lambda syntax).
>>
>> I'd still vote for them to go, but we MUST fix the issue with common
>> instantiation first.
>>
>> There has been some discussion in general of using hashing to decrease
>> the symbol size for templates, and some discussion about allowing the
>> compiler to merge identical binary functions to reduce the size of the
>> binaries. Both of those could play in nicely here.
>
> Yes, you get it exactly right. I think a DIP would be warranted here to
> clarify how lambda equivalence is computed. Could you please draft one?

I started thinking about this, I'm probably not the best to do this by myself, as I'm not familiar with the compiler code. I'm thinking possibly we should talk about this next week in person?

BTW, one of the issues with the DIP system I see is that the table for DIPs is not auto-generated, so there is a stray dip which hasn't been finished and no entry in the table: http://wiki.dlang.org/DIP89

-Steve

April 28, 2016
On Wednesday, 27 April 2016 at 17:04:47 UTC, Andrei Alexandrescu wrote:
> Yes, you get it exactly right. I think a DIP would be warranted here to clarify how lambda equivalence is computed. Could you please draft one? -- Andrei

More generally, it is not clear what is allowed to do for merging functions. In C/C++ it is assumed that different function MUST have different identities. Namely, if foo and bar MUST have a different address.

It means that, even if foo and bar have the same body, you can't merge them. Compiler can, however, emit a branch to foo's body into bar or vice versa and it is alright.

This is a problem for us if we want to merge templates. I think we should abolish this in D, to unlock more merging.
April 28, 2016
On Thursday, 28 April 2016 at 00:14:41 UTC, deadalnix wrote:
> On Wednesday, 27 April 2016 at 17:04:47 UTC, Andrei Alexandrescu wrote:
>> Yes, you get it exactly right. I think a DIP would be warranted here to clarify how lambda equivalence is computed. Could you please draft one? -- Andrei
>
> More generally, it is not clear what is allowed to do for merging functions. In C/C++ it is assumed that different function MUST have different identities. Namely, if foo and bar MUST have a different address.
>
> It means that, even if foo and bar have the same body, you can't merge them. Compiler can, however, emit a branch to foo's body into bar or vice versa and it is alright.
>
> This is a problem for us if we want to merge templates. I think we should abolish this in D, to unlock more merging.

Seconded.
April 28, 2016
On Thursday, 28 April 2016 at 00:14:41 UTC, deadalnix wrote:
> More generally, it is not clear what is allowed to do for merging functions. In C/C++ it is assumed that different function MUST have different identities.

I don't think this needs to hold true for anonymous functions though. If they have the same representation twice, it is arguably just the same function referenced twice.
April 28, 2016
On Thursday, 28 April 2016 at 03:15:36 UTC, Adam D. Ruppe wrote:
> On Thursday, 28 April 2016 at 00:14:41 UTC, deadalnix wrote:
>> More generally, it is not clear what is allowed to do for merging functions. In C/C++ it is assumed that different function MUST have different identities.
>
> I don't think this needs to hold true for anonymous functions though. If they have the same representation twice, it is arguably just the same function referenced twice.

I'm not enough of a language lawyer to know that. But I can tell you that compiler do not take advantage of this at this stage. Maybe it is because lambda are fairly new, I'm not sure.

April 28, 2016
On Wednesday, April 27, 2016 21:18:35 deadalnix via Digitalmars-d wrote:
> On Wednesday, 27 April 2016 at 12:31:18 UTC, Andrei Alexandrescu
>
> wrote:
> > On 04/26/2016 03:45 PM, Jack Stouffer wrote:
> >> I think that the drawback you mentioned does not outweigh the
> >> benefits
> >> gained from using actual lambdas.
> >
> > Actually it turns out to be a major usability issue. -- Andrei
>
> That would be better for this to go forward in a sensible manner that you explains what is the major usability issue here.

Well, it's problematic with default predicates, because you can't test that the default is being used (which some algorithms do for efficiency). You're forced to duplicate the function rather than use a default. But the far bigger problem is when dealing with types that take a predicate (which is going to happen with some containers and many ranges). e.g. this compiles

    RedBlackTree!(int, "a < b") a;
    RedBlackTree!(int, "a < b") b = a;

but this does not:

    RedBlackTree!(int, (a, b) => a < b) a;
    RedBlackTree!(int, (a, b) => a < b) b = a;

You get this wonderfully nonsensical error:

q.d(6): Error: cannot implicitly convert expression (a) of type q.main.RedBlackTree!(int, (a, b) => a < b, false) to q.main.RedBlackTree!(int, (a, b) => a < b, false)

So, you can't convert an expression of one type to an expression of exactly the same type? Sure, the error message could be improved, but I doubt that very many folks are going to expect this sort of error unless they've run into the problem previously. It totally fails to principle of least surprise. And it's definitely a usability problem.

It makes passing around types with predicates a royal pain when you need to type them explicitly (which happens far more with containers than ranges). It also makes having member variables of them harder. To some extent, you can get around it with typeof(a) - which is often what we have to do with ranges anyway, but that's pretty ridiculous when you typed out the exact type to begin with and code-wise, there's no reason why they shouldn't be identical.

There are likely other uses cases where this is a problem, but those are the two that we definitely run into with current Phobos code. And while the problem may make sense to a compiler writer, it's not what much of anyone else is going to expect or want to deal with.

- Jonathan M Davis

April 30, 2016
On Tuesday, 26 April 2016 at 19:45:18 UTC, Jack Stouffer wrote:
> I'm of the opinion that string lambdas must go. I started, and I really should finish it at some point, removing string lambdas from the documentation: https://github.com/dlang/phobos/pull/3800
>
> I think that the drawback you mentioned does not outweigh the benefits gained from using actual lambdas.

I'm personally somewhat fond of string lambdas for their usefulness in making some operations very concise, without sacrificing any readability.

ie.

  foo.map!"a.bar".reduce!"a * b";

vs.

  foo.map!(a => a.bar).reduce!((a, b) => a * b);

I'm open to an alternative that is equally short and sweet, but replacing them with proper lambda declarations feels like uncalled for verbosity to me.
April 30, 2016
On Tuesday, 26 April 2016 at 17:58:22 UTC, Andrei Alexandrescu wrote:
> https://github.com/dlang/phobos/pull/3882
>
> I just closed with some regret a nice piece of engineering. Please comment if you think string lambdas have a lot of unexploited potential.
>
> One thing we really need in order to 100% replace string lambdas with lambdas is function equivalence. Right now we're in the odd situation that SomeTemplate!((a, b) => a < b) has distinct types, one per instantiation.
>
>
> Andrei

An alternative to string lambads could be something like a shortened version of lambdas like consider

"a > b" could be something like {%0 > %1} and then the body of the expression is evaluated to see whether it's a valid D expression where %1 and %2 would equal first and second arguments in the expression and when used you could simply use do something like

Compare(T,expression)(T x, T y) {
    return expression(x,y); // the compiler has to figure out that this should be evaluated as return x < y with the example above.

This will ensure that the expression is a valid D expression and cannot really hacked to do other stuff what it's meant to.

Of course this is just an idea on top of my head on how string lambdas could be resolved.


April 30, 2016
On Saturday, 30 April 2016 at 16:02:02 UTC, Bauss wrote:
> On Tuesday, 26 April 2016 at 17:58:22 UTC, Andrei Alexandrescu wrote:
>> [...]
>
> An alternative to string lambads could be something like a shortened version of lambdas like consider
>
> [...]

%1 and %2 should be %0 and %1 and also the < should be > in the return statement of course.
May 01, 2016
On Saturday, 30 April 2016 at 15:10:08 UTC, Mint wrote:
> On Tuesday, 26 April 2016 at 19:45:18 UTC, Jack Stouffer wrote:
>> I'm of the opinion that string lambdas must go. I started, and I really should finish it at some point, removing string lambdas from the documentation: https://github.com/dlang/phobos/pull/3800
>>
>> I think that the drawback you mentioned does not outweigh the benefits gained from using actual lambdas.
>
> I'm personally somewhat fond of string lambdas for their usefulness in making some operations very concise, without sacrificing any readability.
>
> ie.
>
>   foo.map!"a.bar".reduce!"a * b";
>
> vs.
>
>   foo.map!(a => a.bar).reduce!((a, b) => a * b);
>
> I'm open to an alternative that is equally short and sweet, but replacing them with proper lambda declarations feels like uncalled for verbosity to me.

I really like them too and was impressed that Andrei is so against them, especially if you have more arguments writing the lambda get's more tedious.
The PR was created to support `each` string lambdas with more than two functions, e.g.:

arr.each!`a + 2*b + 4*c + 8*d`
arr.each!((a, b, c, d) = > a + 2*b + 4*c + 8*d)

While I agree that it might be a bit hard to read, they're beautifully concise!
Also they are quite popular - even in Phobos code.

> grep -r unaryFun * | wc -l

142

> grep -r binaryFun * | wc -l

250

> grep -r '!".*"' * --exclude datetime.d | wc -l

591 (disclaimer: some false positives)


Btw at mir we have the convention to use backticks, so it's easier to distinguish them from normal strings - might be a convention that you could adapt or even enforce?