July 03, 2020
On Friday, 3 July 2020 at 17:13:32 UTC, Steven Schveighoffer wrote:
> On 7/3/20 11:25 AM, user1234 wrote:
>> On Thursday, 2 July 2020 at 19:28:15 UTC, Nick Treleaven wrote:
>>> [...]
>> 
>> Wow, that looked cool the first 3 secons but now I wander if  you really want to let the user break the rules of precedence ?
>
> Meh, the user can do anything he wants already. One can get around precedence by deferring calls.
>
> It is true, though, that I didn't think of precedence when I first posted -- I just wanted to avoid some allocations.
>
> -Steve

Your suggestion has no issue with precedence, it's only the version proposed by Nick, as it allows to mix several operators.
July 03, 2020
On Friday, 3 July 2020 at 18:07:08 UTC, Steven Schveighoffer wrote:
> That's what's already done today.
>
> The idea here is to pass all the operations and parameters at once. There are actually advantages in some cases to have the entire expression, as some things could be optimized by reordering operations (in a valid way).
>
> The further question of this subthread is -- can we loosen some of the precedence rules so this provides more capabilities, or would it be crippled by enforcing precedence rules?
>
> -Steve

I think grouping all operations with the same precedence using your new nAryOp idea is a good compromise.  If you need to mess with precedence rules, then you can write expression templates to do so.

I imagine the most common case is string concatenation with a bunch of strings which that approach would handle.

Example:

a + b - c*d/e would become

a.opNaray!(["+", "-"])(a, b, c.opNary!(["*", "/"])(c, d, e))
July 03, 2020
On Friday, 3 July 2020 at 15:39:08 UTC, Andrei Alexandrescu wrote:
> On 7/3/20 3:17 AM, Petar Kirov [ZombineDev] wrote:
>> [..]
>> 
>> We kind of have this already, but a bit closer to what jmh was describing earlier.
>> Though it's just an implementation detail inside druntime for implementing array ops:
>> 
>> https://github.com/dlang/druntime/blob/v2.092.1/src/core/internal/array/operations.d#L15-L36
>> 
>> 
>> I think that it would be pretty cool if this technique could be applied to user-defined types (e.g. allow library authors to provide their own `arrayOp` implementation of their types).
>
> How are parens handled?

Parens don't need special handling since they're already handled by the dmd parser. The frontend simply traverses the expression tree for the array op and outputs it in reverse polish notation here: https://github.com/dlang/dmd/blob/v2.092.1/src/dmd/arrayop.d#L180-L255

For reference, here's the PR by Martin Nowak, who implemented this: https://github.com/dlang/dmd/pull/7032
July 03, 2020
On 7/2/20 12:28 PM, Nick Treleaven wrote:

> Sounds good, although I think multiple other operations beside
> concatenation could benefit from being intercepted:
>
> opNary(string op, T...)(T args)

Given the confusions raised elsewhere in this thread, I think that syntax gives the biggest benefit; defined for a single operator. And we don't even need a new keyword:

  opBinary(string op, T...)(T args)

which would solve Steve's original issue. Of course, doing the expected thing and the associativity issue would still be left to the implementer. For example, in order to stay consistent with the rest of the type system, the programmer should handle b and c before a below because ^^ is right-associative:

  a ^^ b ^^ c

Well, we have to trust the programmer anyway.

So, I propose an extension to opBinary to optionally take multiple arguments.

Ali

July 03, 2020
On 03.07.20 20:39, Ben Jones wrote:
> 
> I think grouping all operations with the same precedence using your new nAryOp idea is a good compromise.  If you need to mess with precedence rules, then you can write expression templates to do so.

Walter's stance on expression templates is that they are operator overloading abuse.
July 03, 2020
On 7/3/20 3:06 PM, Ali Çehreli wrote:
> On 7/2/20 12:28 PM, Nick Treleaven wrote:
> 
>  > Sounds good, although I think multiple other operations beside
>  > concatenation could benefit from being intercepted:
>  >
>  > opNary(string op, T...)(T args)
> 
> Given the confusions raised elsewhere in this thread, I think that syntax gives the biggest benefit; defined for a single operator. And we don't even need a new keyword:
> 
>    opBinary(string op, T...)(T args)

Just nitpicking, but opBinary is not a keyword, so neither would opNary be.

> 
> which would solve Steve's original issue. Of course, doing the expected thing and the associativity issue would still be left to the implementer. For example, in order to stay consistent with the rest of the type system, the programmer should handle b and c before a below because ^^ is right-associative:
> 
>    a ^^ b ^^ c
> 
> Well, we have to trust the programmer anyway.

Any non-commutative operations rely on order of operations. Division, subtraction, etc.

> So, I propose an extension to opBinary to optionally take multiple arguments.

It would be kind of weird/unfortunate for this to support a + b + c, but not a + b - c.

We could still do it all with opBinary, as:

opBinary(string[] ops, Args...)(Args args)

And either enforce precedence by reordering the operations (like Petar suggests) into RPN, or give up when precedence isn't strictly left-to-right. One can always break up the operations into subexpressions and sub-calls to opBinary/opNary.

I think there are sufficient cases which could benefit from this to warrant a consideration for making a DIP. What I'm really more concerned about is pushback on "operator abuse", which clearly this could allow. I wouldn't go forward without some feedback from Walter.

-Steve
July 04, 2020
On Friday, 3 July 2020 at 19:06:21 UTC, Ali Çehreli wrote:
> On 7/2/20 12:28 PM, Nick Treleaven wrote:
>
> > Sounds good, although I think multiple other operations beside
> > concatenation could benefit from being intercepted:
> >
> > opNary(string op, T...)(T args)
>
> Given the confusions raised elsewhere in this thread, I think that syntax gives the biggest benefit; defined for a single operator. And we don't even need a new keyword:
>
>   opBinary(string op, T...)(T args)

That already has a defined behavior:

alias AliasSeq(T...) = T;

struct S {
    AliasSeq!(int, int, int) fields;
    void opBinary(string op, T...)(T args) {
        pragma(msg, T); // (int, int, int)
    }
}

unittest {
    S s;
    s + s.fields;
}

--
  Simen
July 04, 2020
On Friday, 3 July 2020 at 20:27:10 UTC, Steven Schveighoffer wrote:
> It would be kind of weird/unfortunate for this to support a + b + c, but not a + b - c.
>
> We could still do it all with opBinary, as:
>
> opBinary(string[] ops, Args...)(Args args)
>
> And either enforce precedence by reordering the operations (like Petar suggests) into RPN, or give up when precedence isn't strictly left-to-right. One can always break up the operations into subexpressions and sub-calls to opBinary/opNary.

What if the compiler calls opBinaryTemp when it detects an expression that would be used in another opBinary call? Given:

S s1, s2, s3;

`s1 ~ s2 ~ s3` would be lowered to:
`s1.opBinaryTemp!"~"(s2).opBinary!"~"(s3)`

Concatenation is commutative, but for other operations brackets would be handled naturally by the compiler:

`s1 * (s2 + s3)` lowers to:
`s1.opBinary!"*"(s2.opBinaryTemp!"+"(s3))`

The following code would support either 1 concatenation, or 2 concatenations being done at once:

struct S {
    int[] data;

    struct Expr {
        S left, right;

        // left ~ right ~ rhs
        auto opBinary(string op : "~")(S rhs) {
            size_t lhsLen = left.data.len + right.data.len;
            auto r = new int[lhsLen + rhs.data.len];

            r[0..left.data.len] = left.data;
            r[left.data.len..$][0..right.data.len] = right.data;
            r[lhsLen..$][0..rhs.data.len] = rhs.data;
            return r;
        }
    }
    auto opBinaryTemp(string op : "~")(S rhs) {
        return Expr(this, right);
    }
    // this ~ rhs
    auto opBinary(string op : "~")(S rhs) {
        return S(data ~ rhs.data);
    }
}

Maybe this pattern could be extended to work with arbitrary-length sequences of concatenation. Perhaps it could also be extended to work with differing `op` kinds.

Although Expr is essentially an expression template, its use is controlled and the above code doesn't need `alias this`.
July 05, 2020
On Saturday, 4 July 2020 at 12:37:23 UTC, Nick Treleaven wrote:
> What if the compiler calls opBinaryTemp when it detects an expression that would be used in another opBinary call? Given:
>
> S s1, s2, s3;
>
> `s1 ~ s2 ~ s3` would be lowered to:
> `s1.opBinaryTemp!"~"(s2).opBinary!"~"(s3)`

The lowering actually compiles now:
https://github.com/ntrel/stuff/tree/master/optemp

> Maybe this pattern could be extended to work with arbitrary-length sequences of concatenation.

From the above link:

struct S {
    int[] data;

    static struct Expr(T...)
    {
        T items;

        static foreach (E; T)
            static assert(is(E == S));

        // concat(items) ~ rhs
        S opBinary(string op : "~")(S rhs) {
            auto len = rhs.data.length;
            foreach (s; items)
                len += s.data.length;
            auto r = new int[len];

            size_t i;
            foreach (s; items) {
                r[i..$][0..s.data.length] = s.data;
                i += s.data.length;
            }
            r[i..$] = rhs.data;
            return S(r);
        }
        auto opBinaryTemp(string op : "~")(S rhs) {
            return expr(items, rhs);
        }
    }
    private static expr(T...)(T args) {
        return Expr!T(args);
    }
    auto opBinaryTemp(string op : "~")(S rhs) {
        return expr(this, rhs);
    }
    // this ~ rhs
    S opBinary(string op : "~")(S rhs) {
        return expr(this) ~ rhs;
    }
}

void main()
{
    import std.stdio;

    auto s1 = S([1,10]);
    auto s2 = S([2,22]);
    auto s3 = S([3,-3]);
    //s1 ~ s2
    s1.opBinary!"~"(s2).data.writeln;
    //s1 ~ s2 ~ s3
    s1.opBinaryTemp!"~"(s2).opBinary!"~"(s3).data.writeln;
    //s1 ~ s2 ~ s3 ~ s1
    s1.opBinaryTemp!"~"(s2).opBinaryTemp!"~"(s3).opBinary!"~"(s1).data.writeln;
}

1 2 3
Next ›   Last »