digitalmars.D - how is this array subtyping inside struct (bug?) possible?
- mw (34/34) Aug 09 2020 Hi,
- Paul Backus (16/50) Aug 09 2020 No, it's not a bug, it's just a weird quirk of how `alias this`
- mw (27/33) Aug 09 2020 I also tried this:
- RazvanN (6/14) Aug 09 2020 You are accessing the underlying array after it was consumed. The
- mw (43/60) Aug 09 2020 I know that; and that's exactly I call it a subtyping bug: i.e.
- Paul Backus (8/20) Aug 10 2020 You are subtyping a value type (array) with a reference type
- Avrina (8/29) Aug 10 2020 This really doesn't excuse why this bug is happening. Why is
- Kagamin (3/10) Aug 10 2020 When writeln receives a reference type range, are you sure it
- mw (9/20) Aug 10 2020 Because `writeln` should be a *view* (i.e read-only) function, it
- Paul Backus (10/19) Aug 10 2020 The behavior of writeln in this case is documented under
- mw (35/56) Aug 10 2020 https://dlang.org/spec/arrays.html#dynamic-arrays
- mw (3/21) Aug 10 2020 And wait, ... did I saw "__rangeCopy" here? it should be a
- Steven Schveighoffer (24/51) Aug 10 2020 In your code, the type of "range" is SharedArray!(string) which is a
- mw (7/53) Aug 10 2020 This still doesn't explain why the underlying array.length is
- Steven Schveighoffer (29/91) Aug 10 2020 Indeed there is a lot of magic. It's ordinary every-day D magic though ;...
- mw (17/36) Aug 10 2020 This does not work:
- kinke (12/23) Aug 10 2020 This does and prints the desired output:
- mw (2/14) Aug 10 2020 Thanks. I will accept this as the proper work-around.
- Avrina (5/23) Aug 11 2020 There really shouldn't be a workaround, this just outlines that
- Steven Schveighoffer (24/64) Aug 10 2020 No, it is doing exactly what you asked it to do -- turn a non-reference
- mw (8/21) Aug 10 2020 Sorry, I typed too quickly, I'm trying to say: by the
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (6/9) Aug 11 2020 So how should writeln deal with input ranges? Should it simply
- Dominikus Dittes Scherkl (5/6) Aug 11 2020 I would expect writeln to first try to use some const-interface
- Kagamin (14/19) Aug 15 2020 This would disable writeln features people already use, you can
- RazvanN (14/16) Aug 11 2020 Honestly, I think that the real problem here is that alias this
- Alexandru Ermicioi (6/19) Aug 11 2020 Yet, they allow to mask implementation of a range. Phobos
- mw (7/11) Aug 10 2020 compiler => library :-)
- mw (13/14) Aug 10 2020 And the user didn't code this range, the range is provided by the
- Steven Schveighoffer (13/23) Aug 10 2020 It would never do that. Because classes are based on Object, and writeln...
Hi, I want to share an array among a number of structs, with subtyping, I tried this, and find some strange behavior: ``` class SharedArray(T) { public T[] array; alias array this; // subtyping } alias Filenames = SharedArray!(string); struct S { Filenames fns; void alloc() { fns = new Filenames(); } } void main(string[] args) { S s0; s0.alloc(); s0.fns ~= "abc"; foreach (i; 0..3) { S* s1 = new S(); *s1 = s0; // copy the value from s0 to *s1 writeln(s0.fns); } } ``` program output: ``` ["abc"] [] [] ``` why s0.fns changed after copy the value from s0 to *s1? Is this a bug?
Aug 09 2020
On Sunday, 9 August 2020 at 18:45:07 UTC, mw wrote:Hi, I want to share an array among a number of structs, with subtyping, I tried this, and find some strange behavior: ``` class SharedArray(T) { public T[] array; alias array this; // subtyping } alias Filenames = SharedArray!(string); struct S { Filenames fns; void alloc() { fns = new Filenames(); } } void main(string[] args) { S s0; s0.alloc(); s0.fns ~= "abc"; foreach (i; 0..3) { S* s1 = new S(); *s1 = s0; // copy the value from s0 to *s1 writeln(s0.fns); } } ``` program output: ``` ["abc"] [] [] ``` why s0.fns changed after copy the value from s0 to *s1? Is this a bug?No, it's not a bug, it's just a weird quirk of how `alias this` interacts with reference types like classes. When you pass a range to `writeln`, it will iterate the range using `front` and `popFront` in order to print each of the elements. Doing this consumes the range. Normally, that's not a problem, because writeln takes its arguments by value, so any range you pass to it will be copied, and only the copy will be consumed. However, because you've made your `Filenames` class into an input range using `alias this`, and classes are reference types, consuming a copy of the range also consumes the original. The solution is to use the `save` function from `std.range` to create an independent copy of the range for `writeln` to iterate: import std.range; writeln(s0.fns.save);
Aug 09 2020
On Sunday, 9 August 2020 at 20:30:35 UTC, Paul Backus wrote:Normally, that's not a problem, because writeln takes its arguments by value, so any range you pass to it will be copied, and only the copy will be consumed. However, because you've made your `Filenames` class into an input range using `alias this`, and classes are reference types, consuming a copy of the range also consumes the original.I also tried this: ``` foreach (i; 0..3) { S* s1 = new S(); *s1 = s0; writeln(s0.fns); writeln(s0.fns.array.length); // *directly* access array } ``` The output is: ``` ["abc"] 0 [] 0 [] 0 ``` I'm *directly* access the underlying array, so why its length changed to 0 after writeln? So a plain array *is* also a range? I've thought (an array's) range is a *separate* struct (which wrap the original array). The wrapper range can be consumed, but the underlying array should stay the same. I never seen it's mentioned in the doc: https://dlang.org/spec/arrays.html
Aug 09 2020
On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:I'm *directly* access the underlying array, so why its length changed to 0 after writeln?You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.So a plain array *is* also a range? I've thought (an array's) range is a *separate* struct (which wrap the original array). The wrapper range can be consumed, but the underlying array should stay the same. I never seen it's mentioned in the doc: https://dlang.org/spec/arrays.htmlYou had a type in your code.
Aug 09 2020
On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently: ``` foreach (i; 0..3) { S* s1 = new S(); *s1 = s0; // copy the value from s0 to *s1 writeln(s0.fns.array); writeln(s0.fns.array.length); //*directly* access the underlying array } ``` output: ``` ["abc"] 1 ["abc"] 1 ["abc"] 1 ``` via subtyping: ``` foreach (i; 0..3) { S* s1 = new S(); *s1 = s0; // copy the value from s0 to *s1 writeln(s0.fns); writeln(s0.fns.length); } ``` output: ``` ["abc"] 0 [] 0 [] 0 ``` And the direct access is the expected behavior that the subtyping mechanism want to provide a convenience for. Since it introduce a surprise to the user, I'd call that a bug.I'm *directly* access the underlying array, so why its length changed to 0 after writeln?You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.What do you mean? can you elaborate?So a plain array *is* also a range? I've thought (an array's) range is a *separate* struct (which wrap the original array). The wrapper range can be consumed, but the underlying array should stay the same. I never seen it's mentioned in the doc: https://dlang.org/spec/arrays.htmlYou had a type in your code.
Aug 09 2020
On Monday, 10 August 2020 at 02:50:20 UTC, mw wrote:On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:You are subtyping a value type (array) with a reference type (class). So when you directly access it, it's passed by value, but when you pass the subtype, it's passed by reference. In other words, using `alias this` does not create a *true* subtype (according to the Liskov substitution principle), because it allows you to change the way the type is copied from by-value to by-reference.On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently:I'm *directly* access the underlying array, so why its length changed to 0 after writeln?You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
Aug 10 2020
On Monday, 10 August 2020 at 12:03:06 UTC, Paul Backus wrote:On Monday, 10 August 2020 at 02:50:20 UTC, mw wrote:This really doesn't excuse why this bug is happening. Why is writeln() using front() and popFront() that modifies the range rather than simply using foreach()? Why is it modifying something it knows to be a class? This is definitely a bug that should be fixed. Not sure why people are trying to explain it away, looking that the writeln implementation it is completely convoluted. It's no wonder bugs like this are going to happen.On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:You are subtyping a value type (array) with a reference type (class). So when you directly access it, it's passed by value, but when you pass the subtype, it's passed by reference. In other words, using `alias this` does not create a *true* subtype (according to the Liskov substitution principle), because it allows you to change the way the type is copied from by-value to by-reference.On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently:I'm *directly* access the underlying array, so why its length changed to 0 after writeln?You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
Aug 10 2020
On Monday, 10 August 2020 at 13:44:17 UTC, Avrina wrote:This really doesn't excuse why this bug is happening. Why is writeln() using front() and popFront() that modifies the range rather than simply using foreach()? Why is it modifying something it knows to be a class? This is definitely a bug that should be fixed. Not sure why people are trying to explain it away, looking that the writeln implementation it is completely convoluted. It's no wonder bugs like this are going to happen.When writeln receives a reference type range, are you sure it shouldn't be consumed? How do you decide that?
Aug 10 2020
On Monday, 10 August 2020 at 15:36:35 UTC, Kagamin wrote:On Monday, 10 August 2020 at 13:44:17 UTC, Avrina wrote:Because `writeln` should be a *view* (i.e read-only) function, it shouldn't *internally* call anything thing that change the data passed in. Only the user can *explicitly* change the data, and let writeln print it: e.g. ``` writeln(data.change_And_Print_The_State_After_The_Change()); ```This really doesn't excuse why this bug is happening. Why is writeln() using front() and popFront() that modifies the range rather than simply using foreach()? Why is it modifying something it knows to be a class? This is definitely a bug that should be fixed. Not sure why people are trying to explain it away, looking that the writeln implementation it is completely convoluted. It's no wonder bugs like this are going to happen.When writeln receives a reference type range, are you sure it shouldn't be consumed? How do you decide that?
Aug 10 2020
On Monday, 10 August 2020 at 13:44:17 UTC, Avrina wrote:This really doesn't excuse why this bug is happening. Why is writeln() using front() and popFront() that modifies the range rather than simply using foreach()?foreach over a range also uses front() and popFront().Why is it modifying something it knows to be a class? This is definitely a bug that should be fixed.The behavior of writeln in this case is documented under `std.format.formatValue` [1], which is used by `writeln` internally:For the class objects which have input range interface, * If the instance toString has overridden Object.toString, it is used. * Otherwise, the objects are formatted as input range.So everything is working as intended. However, there is certainly a documentation issue here, since it is not at all obvious from the `writeln` docs that it uses `formatValue`. [1] http://dpldocs.info/experimental-docs/std.format.formatValue.html
Aug 10 2020
On Monday, 10 August 2020 at 12:03:06 UTC, Paul Backus wrote:On Monday, 10 August 2020 at 02:50:20 UTC, mw wrote:https://dlang.org/spec/arrays.html#dynamic-arrays "Dynamic arrays consist of a length and a pointer to the array data." I've thought, D's dynamic array is implemented as: ``` /* this is C code */ struct { T* ptr; size_t length; } ``` And now, it shows the length is changeable via its range interface: so where on this range doc page this behavior on length is mentioned? https://dlang.org/phobos/std_range_primitives.html "It defines the bidirectional and forward range primitives for arrays: empty, front, back, popFront, popBack and save." and where in this doc on range: https://tour.dlang.org/tour/en/basics/ranges it is mentioned that: the length property of the range will be changed after the range is "consumed" by foreach loop? ``` foreach (element; range) { // Loop body... } it's internally rewritten similar to the following: for (auto __rangeCopy = range; !__rangeCopy.empty; __rangeCopy.popFront()) { auto element = __rangeCopy.front; // Loop body... } ```On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:You are subtyping a value type (array) with a reference type (class). So when you directly access it, it's passed by value, but when you pass the subtype, it's passed by reference. In other words, using `alias this` does not create a *true* subtype (according to the Liskov substitution principle), because it allows you to change the way the type is copied from by-value to by-reference.On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently:I'm *directly* access the underlying array, so why its length changed to 0 after writeln?You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
Aug 10 2020
On Monday, 10 August 2020 at 18:29:56 UTC, mw wrote:and where in this doc on range: https://tour.dlang.org/tour/en/basics/ranges it is mentioned that: the length property of the range will be changed after the range is "consumed" by foreach loop? ``` foreach (element; range) { // Loop body... } it's internally rewritten similar to the following: for (auto __rangeCopy = range; !__rangeCopy.empty; __rangeCopy.popFront()) { auto element = __rangeCopy.front; // Loop body... } ```And wait, ... did I saw "__rangeCopy" here? it should be a *copy*?!
Aug 10 2020
On 8/10/20 2:36 PM, mw wrote:On Monday, 10 August 2020 at 18:29:56 UTC, mw wrote:In your code, the type of "range" is SharedArray!(string) which is a class or *reference type*. So let's follow along what happens: SharedArray!(string) fns; for(auto __rangeCopy = fns; // the above makes a copy of a *class reference*, which means it does not make a copy of the *array data* or even the array reference. It's like copying a pointer to the array. !__rangeCopy.empty; // SharedArray(T) does not have empty member, so this forwards to the array, it's like saying: // !__rangeCopy.array.empty __rangeCopy.popFront()) // This is equivalent to __rangeCopy.array.popFront, which alters the array inside the ONE SHARED class instance. So now, what ends up happening is because fns and the __rangeCopy are actually just copies of a pointer, or class reference, when you iterate one, the other is iterated. How to fix? extract the array before iterating: writeln(s0.fns.array); This will make a copy of the array reference itself, and iterate that. This is precisely why classes should never be ranges. -Steveand where in this doc on range: https://tour.dlang.org/tour/en/basics/ranges it is mentioned that: the length property of the range will be changed after the range is "consumed" by foreach loop? ``` foreach (element; range) { // Loop body... } it's internally rewritten similar to the following: for (auto __rangeCopy = range; !__rangeCopy.empty; __rangeCopy.popFront()) { auto element = __rangeCopy.front; // Loop body... } ```And wait, ... did I saw "__rangeCopy" here? it should be a *copy*?!
Aug 10 2020
On Monday, 10 August 2020 at 19:30:18 UTC, Steven Schveighoffer wrote:On 8/10/20 2:36 PM, mw wrote:This still doesn't explain why the underlying array.length is modified after the range to consumed; too much black magic is happening here.On Monday, 10 August 2020 at 18:29:56 UTC, mw wrote:In your code, the type of "range" is SharedArray!(string) which is a class or *reference type*. So let's follow along what happens: SharedArray!(string) fns; for(auto __rangeCopy = fns; // the above makes a copy of a *class reference*, which means it does not make a copy of the *array data* or even the array reference. It's like copying a pointer to the array. !__rangeCopy.empty; // SharedArray(T) does not have empty member, so this forwards to the array, it's like saying: // !__rangeCopy.array.empty __rangeCopy.popFront()) // This is equivalent to __rangeCopy.array.popFront, which alters the array inside the ONE SHARED class instance.and where in this doc on range: https://tour.dlang.org/tour/en/basics/ranges it is mentioned that: the length property of the range will be changed after the range is "consumed" by foreach loop? ``` foreach (element; range) { // Loop body... } it's internally rewritten similar to the following: for (auto __rangeCopy = range; !__rangeCopy.empty; __rangeCopy.popFront()) { auto element = __rangeCopy.front; // Loop body... } ```And wait, ... did I saw "__rangeCopy" here? it should be a *copy*?!How to fix? extract the array before iterating: writeln(s0.fns.array);This defeats the purpose, i.e. the convenience that subtyping mechanism supposed to provide.
Aug 10 2020
On 8/10/20 3:39 PM, mw wrote:On Monday, 10 August 2020 at 19:30:18 UTC, Steven Schveighoffer wrote:Indeed there is a lot of magic. It's ordinary every-day D magic though ;) string[] arr = ["abc"]; arr.popFront; // ufcs function defined in std.range.primitives assert(arr.length == 0); // reduces the length If we inlined this fully with the definition of std.range.primitives.popFront, the line: __rangeCopy.popFront() is really doing: __rangeCopy.array = __rangeCopy.array[1 .. $]; This is how you iterate an array as a range.On 8/10/20 2:36 PM, mw wrote:This still doesn't explain why the underlying array.length is modified after the range to consumed; too much black magic is happening here.On Monday, 10 August 2020 at 18:29:56 UTC, mw wrote:In your code, the type of "range" is SharedArray!(string) which is a class or *reference type*. So let's follow along what happens: SharedArray!(string) fns; for(auto __rangeCopy = fns; // the above makes a copy of a *class reference*, which means it does not make a copy of the *array data* or even the array reference. It's like copying a pointer to the array. !__rangeCopy.empty; // SharedArray(T) does not have empty member, so this forwards to the array, it's like saying: // !__rangeCopy.array.empty __rangeCopy.popFront()) // This is equivalent to __rangeCopy.array.popFront, which alters the array inside the ONE SHARED class instance.and where in this doc on range: https://tour.dlang.org/tour/en/basics/ranges it is mentioned that: the length property of the range will be changed after the range is "consumed" by foreach loop? ``` foreach (element; range) { // Loop body... } it's internally rewritten similar to the following: for (auto __rangeCopy = range; !__rangeCopy.empty; __rangeCopy.popFront()) { auto element = __rangeCopy.front; // Loop body... } ```And wait, ... did I saw "__rangeCopy" here? it should be a *copy*?!You are subtyping but inadvertently have turned a forward range (array) into an input range (iterate only once) by changing it into a class. forward ranges aren't supposed to technically be valid if you don't *save* them, but in practice it's totally fine for arrays. What might work (and I haven't tried this) is to disable front, popFront, empty, and I think what will then happen is the compiler will try slicing instead (which should work) and iterate a copy of the array. e.g.: class SharedArray!T { T[] array; alias array this; disable: front(); popFront(); empty(); } -SteveHow to fix? extract the array before iterating: writeln(s0.fns.array);This defeats the purpose, i.e. the convenience that subtyping mechanism supposed to provide.
Aug 10 2020
This subtyping loophole should be fixed by the compiler.This defeats the purpose, i.e. the convenience that subtyping mechanism supposed to provide.You are subtyping but inadvertently have turned a forward range (array) into an input range (iterate only once) by changing it into a class.What might work (and I haven't tried this) is to disable front, popFront, empty, and I think what will then happen is the compiler will try slicing instead (which should work) and iterate a copy of the array. e.g.: class SharedArray!T { T[] array; alias array this; disable: front(); popFront(); empty(); }This does not work: ``` Error: no identifier for declarator front() Error: function declaration without return type. (Note that constructors are always named this) Error: no identifier for declarator popFront() Error: function declaration without return type. (Note that constructors are always named this) Error: no identifier for declarator empty() ``` looks like ` disable` expect the full function implementation with body: https://dlang.org/spec/attribute.html#disable BTW, this way of (thinking) fixing the problem is one step closer to Eiffel's mechanism: i.e. member level tailored inheritance. Sigh, this is not done earlier into the language.
Aug 10 2020
On Monday, 10 August 2020 at 20:13:42 UTC, mw wrote:This does and prints the desired output: class SharedArray(T) { T[] array; alias array this; final disable: T front(); void popFront(); bool empty(); } https://run.dlang.io/is/5ciCueclass SharedArray!T { T[] array; alias array this; disable: front(); popFront(); empty(); }This does not work:
Aug 10 2020
On Monday, 10 August 2020 at 20:34:33 UTC, kinke wrote:Thanks. I will accept this as the proper work-around.This does not work:This does and prints the desired output: class SharedArray(T) { T[] array; alias array this; final disable: T front(); void popFront(); bool empty(); } https://run.dlang.io/is/5ciCue
Aug 10 2020
On Monday, 10 August 2020 at 21:15:58 UTC, mw wrote:On Monday, 10 August 2020 at 20:34:33 UTC, kinke wrote:There really shouldn't be a workaround, this just outlines that there's an ordering problem in writeln. It should be using front/popfront as a last resort if it isn't able to use another (better) method.Thanks. I will accept this as the proper work-around.This does not work:This does and prints the desired output: class SharedArray(T) { T[] array; alias array this; final disable: T front(); void popFront(); bool empty(); } https://run.dlang.io/is/5ciCue
Aug 11 2020
On 8/10/20 4:13 PM, mw wrote:No, it is doing exactly what you asked it to do -- turn a non-reference type into a full reference type. The fault here is Phobos for accepting classes as ranges (range classes are IMO an abomination that should never be used).This subtyping loophole should be fixed by the compiler.This defeats the purpose, i.e. the convenience that subtyping mechanism supposed to provide.You are subtyping but inadvertently have turned a forward range (array) into an input range (iterate only once) by changing it into a class.Ugh, no, I just forgot to put in the types: disable: T front(); void popFront(); bool empty(); And I figured I'd try it out rather than use you as a REPL ;) They also need final to avoid putting the non-existent functions in the vtable. This works (and prints what you want): class SharedArray(T) { T[] array; alias array this; disable final: T front(); void popFront(); bool empty(); } -SteveWhat might work (and I haven't tried this) is to disable front, popFront, empty, and I think what will then happen is the compiler will try slicing instead (which should work) and iterate a copy of the array. e.g.: class SharedArray!T { T[] array; alias array this; disable: front(); popFront(); empty(); }This does not work: ``` Error: no identifier for declarator front() Error: function declaration without return type. (Note that constructors are always named this) Error: no identifier for declarator popFront() Error: function declaration without return type. (Note that constructors are always named this) Error: no identifier for declarator empty() ``` looks like ` disable` expect the full function implementation with body:
Aug 10 2020
On Monday, 10 August 2020 at 20:42:34 UTC, Steven Schveighoffer wrote:On 8/10/20 4:13 PM, mw wrote:Sorry, I typed too quickly, I'm trying to say: by the __compiler__ -> by the std library.No, it is doing exactly what you asked it to do -- turn a non-reference type into a full reference type.This subtyping loophole should be fixed by the compiler.This defeats the purpose, i.e. the convenience that subtyping mechanism supposed to provide.You are subtyping but inadvertently have turned a forward range (array) into an input range (iterate only once) by changing it into a class.The fault here is Phobos for accepting classes as ranges (range classes are IMO an abomination that should never be used).Yes, it's the library did it wrong, I still hold the opinion that: `writeln` should be a *view* (i.e read-only) function, it shouldn't *internally* call anything thing that change the data passed in.
Aug 10 2020
On Monday, 10 August 2020 at 21:20:11 UTC, mw wrote:`writeln` should be a *view* (i.e read-only) function, it shouldn't *internally* call anything thing that change the data passed in.So how should writeln deal with input ranges? Should it simply not be possible to print them, as that would (by their very nature) change them? -- Simen
Aug 11 2020
On Tuesday, 11 August 2020 at 13:36:19 UTC, Simen Kjærås wrote:So how should writeln deal with input ranges?I would expect writeln to first try to use some const-interface (e.g. indexing if the given type provides that). Only if such interface is not available, it should try modifying interfaces like range pop-front.
Aug 11 2020
On Monday, 10 August 2020 at 21:20:11 UTC, mw wrote:Yes, it's the library did it wrong, I still hold the opinion that: `writeln` should be a *view* (i.e read-only) function, it shouldn't *internally* call anything thing that change the data passed in.This would disable writeln features people already use, you can create a simple const-flavored writeln wrapper: void writeln(Args...)(in Args a) { static import std.stdio; std.stdio.writeln(a); } int main(string[] args) { Filenames a=new Filenames; writeln(a); return 0; }
Aug 15 2020
On Monday, 10 August 2020 at 20:42:34 UTC, Steven Schveighoffer wrote:The fault here is Phobos for accepting classes as ranges (range classes are IMO an abomination that should never be used).Honestly, I think that the real problem here is that alias this can be used with classes. I would argue that alias this should be available only for structs because: 1. There are no rules that define precedence: which should have priority alias this or one of the parents? 2. For classes you already have the inheritance mechanism and therefore alias this basically invites multiple inheritance into the problem (with its known issues: the diamond problem). It would be easier for everyone if alias this would be banned and classes and just stick to structs. Cheers, RazvanN
Aug 11 2020
On Monday, 10 August 2020 at 20:42:34 UTC, Steven Schveighoffer wrote:On 8/10/20 4:13 PM, mw wrote:Yet, they allow to mask implementation of a range. Phobos shouldn't just prohibit all class based ranges, just those that don't implement right interfaces, such as InputRange(T) or ForwardRange(T).No, it is doing exactly what you asked it to do -- turn a non-reference type into a full reference type. The fault here is Phobos for accepting classes as ranges (range classes are IMO an abomination that should never be used).This subtyping loophole should be fixed by the compiler.This defeats the purpose, i.e. the convenience that subtyping mechanism supposed to provide.You are subtyping but inadvertently have turned a forward range (array) into an input range (iterate only once) by changing it into a class.
Aug 11 2020
On Monday, 10 August 2020 at 19:52:19 UTC, Steven Schveighoffer wrote:What might work (and I haven't tried this) is to disable front, popFront, empty, and I think what will then happen is the compiler will try slicing instead (which should work) and iterate a copy of the array.compiler => library :-) BTW, where is the code of this interface testing sequence? I think we should change the testing order to try slicing first, because slice is a “view” (readonly to the underlying data) struct.
Aug 10 2020
On Monday, 10 August 2020 at 19:30:18 UTC, Steven Schveighoffer wrote:This is precisely why classes should never be ranges.And the user didn't code this range, the range is provided by the language standard library: https://dlang.org/phobos/std_range_primitives.html it's the std library that didn't take into account of the array-be-subtyped-as class loop-hole. Ideally, to the user, subtyping should just perform simple forward to the underlying data member, i.e. writeln(s0.fns); should be translated by the *compiler* to: writeln(s0.fns.array); Obviously it failed to do so here.
Aug 10 2020
On 8/10/20 3:49 PM, mw wrote:Ideally, to the user, subtyping should just perform simple forward to the underlying data member, i.e. writeln(s0.fns); should be translated by the *compiler* to: writeln(s0.fns.array); Obviously it failed to do so here.It would never do that. Because classes are based on Object, and writeln is defined to accept an Object and print it that way (using Object.toString). So even if the range methods weren't available, it wouldn't instead pass the base type. What's happening is that inadvertently, you have created a range type. writeln is going to PREFER using range mechanisms over Object.toString, so when it sees it can use range primitives, it uses those. This is the danger of duck-typing. What you want is array functionality, not range functionality on the class. So you need to avoid those mechanisms. Try the disable idea, see if it works. -Steve
Aug 10 2020