Thread overview | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
May 14, 2019 New operators opStaticIndex and friends | ||||
---|---|---|---|---|
| ||||
I whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSlice When used, they look the same way as opIndex and friends, but the stuff between [ and ] would be bound to template parameters (instead of runtime parameters) and be known at compile time. Basically, this allows to implement static indexing the same way it is possible for an AliasSeq. Furthermore, it is possible to have static and dynamic indexing next to each other. This is supported by static-size arrays (e.g. int[3]) only: https://run.dlang.io/is/99g01e Implementing e.g. tuple-like structures, it would be valuable to have both. Dynamic indexing would only be present (design by introspection) if the contents support it. struct S { enum int opStaticIndex(int index) = index + 1; int opIndex(int index) { return index + 1; } enum size_t[2] opStaticSlice(size_t i, size_t j) = [ i, j ]; void opStaticIndexAssign(int index)(int value); void opStaticIndexAssign(int i, int j)(int value); void opStaticIndexAssign(size_t[2] slice)(int value); void opStaticIndexOpAssign(string op, int index)(int value); } S s; int i = s[0]; // rewrites to s.opStaticIndex!(0), as 0 can be used for the template parameter int j = s[i]; // rewrites to s.opIndex(i), as i cannot be read at compile-time The "Static" variants would be preferred when determining which one to lower to. s[0] = 1; // rewrites to s.opStaticIndexAssign!(0)(1) s[0, 2] = 1; // rewrites to s.opStaticIndexAssign!(0, 2)(1) s[0 .. 2] = 1; // rewrites to s.opStaticIndexAssign!(s.opStaticSlice!(0, 2))(1) s[0] += 1; // rewrites to s.opStaticIndexOpAssign!("+", 0)(1) s[0, 2] += 1; // rewrites to s.opStaticIndexOpAssign!("+", 0, 2)(1) Currently, there is no way to implement custom compile-time indexing. Only using alias-this to an AliasSeq can do something, but is very limited. The alias-this is shadowed by any presence of opIndex. An alternative would be a `static` or `enum` storage class for function runtime parameters that only bind to values known at compile-time. These have been proposed years ago and would not only solve this but also format!"fmt" vs. format(fmt) and friends. I have no knowledge about the DMD implementation, but I'd intuitively expect that new operator rewrites would be much less work to implement. Do you want it? Do you see problems? Should I clarify something? Is it worth writing a DIP? |
May 14, 2019 Re: New operators opStaticIndex and friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Q. Schroll | On Tue, May 14, 2019 at 03:49:51PM +0000, Q. Schroll via Digitalmars-d wrote: > I whish, D has these operators: > > opStaticIndex > opStaticIndexAssign > opStaticIndexOpAssign > opStaticSlice > > When used, they look the same way as opIndex and friends, but the stuff between [ and ] would be bound to template parameters (instead of runtime parameters) and be known at compile time. Basically, this allows to implement static indexing the same way it is possible for an AliasSeq. +1. I'd love to have this feature too. T -- Don't throw out the baby with the bathwater. Use your hands... |
May 14, 2019 Re: New operators opStaticIndex and friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Q. Schroll | On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote:
> I whish, D has these operators:
>
> opStaticIndex
> opStaticIndexAssign
> opStaticIndexOpAssign
> opStaticSlice
>
> When used, they look the same way as opIndex and friends, but the stuff between [ and ] would be bound to template parameters (instead of runtime parameters) and be known at compile time. Basically, this allows to implement static indexing the same way it is possible for an AliasSeq.
>
> Furthermore, it is possible to have static and dynamic indexing next to each other. This is supported by static-size arrays (e.g. int[3]) only: https://run.dlang.io/is/99g01e
>
> Implementing e.g. tuple-like structures, it would be valuable to have both. Dynamic indexing would only be present (design by introspection) if the contents support it.
>
> struct S
> {
> enum int opStaticIndex(int index) = index + 1;
> int opIndex(int index) { return index + 1; }
>
> enum size_t[2] opStaticSlice(size_t i, size_t j) = [ i, j ];
>
> void opStaticIndexAssign(int index)(int value);
> void opStaticIndexAssign(int i, int j)(int value);
> void opStaticIndexAssign(size_t[2] slice)(int value);
> void opStaticIndexOpAssign(string op, int index)(int value);
> }
>
> S s;
> int i = s[0]; // rewrites to s.opStaticIndex!(0), as 0 can be used for the template parameter
> int j = s[i]; // rewrites to s.opIndex(i), as i cannot be read at compile-time
>
> The "Static" variants would be preferred when determining which one to lower to.
>
> s[0] = 1; // rewrites to s.opStaticIndexAssign!(0)(1)
> s[0, 2] = 1; // rewrites to s.opStaticIndexAssign!(0, 2)(1)
> s[0 .. 2] = 1; // rewrites to s.opStaticIndexAssign!(s.opStaticSlice!(0, 2))(1)
> s[0] += 1; // rewrites to s.opStaticIndexOpAssign!("+", 0)(1)
> s[0, 2] += 1; // rewrites to s.opStaticIndexOpAssign!("+", 0, 2)(1)
>
> Currently, there is no way to implement custom compile-time indexing. Only using alias-this to an AliasSeq can do something, but is very limited. The alias-this is shadowed by any presence of opIndex.
>
> An alternative would be a `static` or `enum` storage class for function runtime parameters that only bind to values known at compile-time.
> These have been proposed years ago and would not only solve this but also format!"fmt" vs. format(fmt) and friends.
>
> I have no knowledge about the DMD implementation, but I'd intuitively expect that new operator rewrites would be much less work to implement.
>
> Do you want it? Do you see problems? Should I clarify something? Is it worth writing a DIP?
You say:
int i = s[0]; // rewrites to s.opStaticIndex!(0), as 0 can be
int j = s[i]; // rewrites to s.opIndex(i), as i cannot be read at compile-time
But actually, in this example, s[i] CAN be figured out at compile-time.
What is even more problematic is that the ability of the compiler to figure out the value of 'i' and s[i] at compile-time might depend on the optimization level, thus you cannot be sure whether s.opStaticIndex!(1) or s.opIndex(i) will be called.
IMHO, s[0] is already calculated in compile-time when appropriate optimization level is invoked ... at least it should've been.
|
May 14, 2019 Re: New operators opStaticIndex and friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to angel | On Tue, May 14, 2019 at 05:27:25PM +0000, angel via Digitalmars-d wrote: [...] > You say: > int i = s[0]; // rewrites to s.opStaticIndex!(0), as 0 can be > int j = s[i]; // rewrites to s.opIndex(i), as i cannot be read at > compile-time > > But actually, in this example, s[i] CAN be figured out at compile-time. What is even more problematic is that the ability of the compiler to figure out the value of 'i' and s[i] at compile-time might depend on the optimization level, thus you cannot be sure whether s.opStaticIndex!(1) or s.opIndex(i) will be called. [...] I think you're being misled by the ambiguous term "compile-time", which can refer to very different things. See: https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time In D, indexing with a variable is not considered "compile-time" in the sense of AST manipulation (see above article for definition), although it *can* be evaluated in CTFE. AIUI, opStaticIndex is intended to be used at the AST manipulation phase, since the usual "runtime" array indexing is already supported in CTFE. This has nothing to do with optimization levels, which happens in codegen, long past any meaningful usage of compile-time computation as far as AST manipulation or CTFE is concerned. T -- Too many people have open minds but closed eyes. |
May 14, 2019 Re: New operators opStaticIndex and friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Q. Schroll | On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote:
> I whish, D has these operators:
>
> opStaticIndex
> opStaticIndexAssign
> opStaticIndexOpAssign
> opStaticSlice
I think this is solving the wrong problem. If compiler provided means to check whether a parameter is known at compile time easily & consistently these would not be necessary at all. Of course, talk is cheap and actually implementing such new __traits is rather compilcated.
|
May 14, 2019 Re: New operators opStaticIndex and friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to angel | On Tuesday, 14 May 2019 at 17:27:25 UTC, angel wrote: > You say: > int i = s[0]; // rewrites to s.opStaticIndex!(0), as 0 can be > int j = s[i]; // rewrites to s.opIndex(i), as i cannot be read at compile-time > > But actually, in this example, s[i] CAN be figured out at compile-time. You get it wrong: The fact that `i` can be figured out by some clever compiler is irrelevant. You couldn't assign it to some static immutable or enum. You couldn't use it as a template parameter. This is not about clever optimizations, it is about wether the spec says, the value is present at compile time. > What is even more problematic is that the ability of the compiler to figure out the value of 'i' and s[i] at compile-time might depend on the optimization level, thus you cannot be sure whether s.opStaticIndex!(1) or s.opIndex(i) will be called. > > IMHO, s[0] is already calculated in compile-time when appropriate optimization level is invoked ... at least it should've been. If `seq` is a non-empty AliasSeq, you can use seq[0]. You cannot use seq[i] for `i` defined as above. The spec says that `i`'s value is determined at runtime, i.e. when the compiled program is executed. If the compiler's optimizer can figure out that it knows the value beforehand, it won't make the thing compile. When talking about language specification, one rarely considers optimizations. |
May 14, 2019 Re: New operators opStaticIndex and friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ahmet Sait | On Tue, May 14, 2019 at 06:13:18PM +0000, Ahmet Sait via Digitalmars-d wrote: > On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote: > > I whish, D has these operators: > > > > opStaticIndex > > opStaticIndexAssign > > opStaticIndexOpAssign > > opStaticSlice > > I think this is solving the wrong problem. If compiler provided means to check whether a parameter is known at compile time easily & consistently these would not be necessary at all. Of course, talk is cheap and actually implementing such new __traits is rather compilcated. Again, people are getting confused by the overloaded term "compile-time". People really need to read this: https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time (Sorry for the shameless self-plug.) opStaticIndex is necessary because it pertains to the *AST manipulation* phase of compilation, whereas opIndex pertains the executable phase (CTFE and/or runtime). It's meaningless to "check whether a parameter is known at compile time" because you cannot go back to the AST manipulation stage after you have passed semantic analysis, which is when types are resolved and things like VRP are applied. T -- Beware of bugs in the above code; I have only proved it correct, not tried it. -- Donald Knuth |
May 14, 2019 Re: New operators opStaticIndex and friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ahmet Sait | On Tuesday, 14 May 2019 at 18:13:18 UTC, Ahmet Sait wrote: > On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote: >> I whish, D has these operators: >> >> opStaticIndex >> opStaticIndexAssign >> opStaticIndexOpAssign >> opStaticSlice > > I think this is solving the wrong problem. Maybe. I'll try to clarify what problem is being solved. Current state of D: Let seq be some AliasSeq. Indexing it looks like this: auto value = seq[i]; Here, the compiler must evaluate `i` at compile-time (cf. H. S. Teoh's link) to even get the type of value correctly. Let arr be some slice (aka. "dynamic array"), e.g. a value of int[]. Indexing it looks like this: auto value = arr[i]; Wow, they look identically, but mean completely different things! What an incident. You can make the latter work in your custom type (notably random-access ranges) by providing opIndex and companions. However, you cannot provide the former functionality for your custom type. The suggested operators would solve that without interrupting the rest of the language. > If compiler provided means to check whether a parameter is known at compile time easily & consistently these would not be necessary at all What kind of parameter? A template parameter is known at compile-time by definition and a function (run-time) parameter by definition is not. The new operators won't change that. > Of course, talk is cheap and actually implementing such new __traits is rather compilcated. This is no kind of __traits. I don't think the problem could be solved nicely with such __traits if it existed. The alternative were a parameter *storage class*: T opIndex(static size_t i); // alternative to opStaticIndex T opIndex( size_t i); An illustrative example: void format(static string fmt, ...); //1 void format( string fmt, ...); //2 You'd have both overloads. //1 is viable, when the first parameter can be evaluated at compile-time. It could check at compile-time if the arguments' types match the format. //2 is always viable, but //1 is considered a better match for a compile-time format string. The `static` storage class would make the parameter semantically equivalent to a template parameter inside the `format` function. Outside, it would be called using the same syntax, i.e. string s1 = format("The %s-th component", i); // calls //1 string s2 = format(readln(), i); // calls //2 You basically could overload on the compile-time availableness of parameters. There is no __traits to be involved. Even without compiler knowledge, I strongly believe that this is more complicated, apart from being a major change of the language. |
May 14, 2019 Re: New operators opStaticIndex and friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On Tuesday, 14 May 2019 at 18:35:51 UTC, H. S. Teoh wrote:
> On Tue, May 14, 2019 at 06:13:18PM +0000, Ahmet Sait via Digitalmars-d wrote:
>> On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote:
>> > I whish, D has these operators:
>> >
>> > opStaticIndex
>> > opStaticIndexAssign
>> > opStaticIndexOpAssign
>> > opStaticSlice
>>
>> I think this is solving the wrong problem. If compiler provided means to check whether a parameter is known at compile time easily & consistently these would not be necessary at all. Of course, talk is cheap and actually implementing such new __traits is rather compilcated.
>
> Again, people are getting confused by the overloaded term "compile-time". People really need to read this:
>
> https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time
>
> (Sorry for the shameless self-plug.)
>
> opStaticIndex is necessary because it pertains to the *AST manipulation* phase of compilation, whereas opIndex pertains the executable phase (CTFE and/or runtime). It's meaningless to "check whether a parameter is known at compile time" because you cannot go back to the AST manipulation stage after you have passed semantic analysis, which is when types are resolved and things like VRP are applied.
>
>
> T
I've read it before, and read it again now just to be sure. Obviously such functions (the ones that check whether a variable is known at CT) need to be templates and get instantiated on every call to actually work.
Something close to this:
auto opIndex()(size_t i)
{
enum types = [ "double", "long", "int" ];
static if (__traits(isKnownAtCT, i))
{
static if (i < types.length)
return mixin("cast(" ~ types[i] ~ ")i");
else
return "Here be dragons";
}
}
|
May 14, 2019 Re: New operators opStaticIndex and friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ahmet Sait | On Tue, May 14, 2019 at 08:07:03PM +0000, Ahmet Sait via Digitalmars-d wrote: [...] > I've read it before, and read it again now just to be sure. Obviously such functions (the ones that check whether a variable is known at CT) need to be templates and get instantiated on every call to actually work. Templates are not instantiated once per call. They are only instantiated once per unique combination of argument types. > Something close to this: > > auto opIndex()(size_t i) This doesn't work, as the compiler sees `i` as a runtime parameter. When compiling this code, the compiler doesn't know (nor care) where the argument might have come from. It can't, because this function could potentially be called from an external module separately compiled, or even bound only at runtime via a dynamic library load. Also, since the template parameter list is empty, this template will only be instantiated once throughout the entire program. So the static if below won't work as you want it to. > { > enum types = [ "double", "long", "int" ]; > static if (__traits(isKnownAtCT, i)) > { > static if (i < types.length) > return mixin("cast(" ~ types[i] ~ ")i"); > else > return "Here be dragons"; > } > } Something closer to what you're looking for might be this: auto opIndex(alias i)() if (is(typeof(i) == size_t) { ... } This will get instantiated once per call, but will require !() syntax rather than the usual, so the way the [] operator is overloaded will have to be extended. Also, `i` in this scope will refer to the actual variable in the caller's scope, so if the function body modifies it you could get very strange results. And I"m not 100% sure this will work for all possible argument types; the alias parameter may not accept arbitrary expressions due to various limitations, so you might end up with the strange situation that opIndex!0 works but opIndex!(1-1) doesn't. T -- English is useful because it is a mess. Since English is a mess, it maps well onto the problem space, which is also a mess, which we call reality. Similarly, Perl was designed to be a mess, though in the nicest of all possible ways. -- Larry Wall |
Copyright © 1999-2021 by the D Language Foundation