What is the 'Result' type even for?
Take this example: ```d import std; void main() { auto c = "a|b|c|d|e".splitter('|'); c.writeln; string[] e = ["a", "b", "c", "d", "e"]; assert(c.equal(e)); typeof(c).stringof.writeln; } ``` The program prints: ["a", "b", "c", "d", "e"] Result What is the purpose of this 'Result' type? To serve as a generic range? Because, it seems to only cause problems. For example, you cannot assign or cast the result type into a range, even when the type has the same inherent function: ```d string[] c = "a|b|c|d|e".splitter('|'); // fails string[] d = cast(string[])"a|b|c|d|e".splitter('|'); // also fails ``` And if you need to perform a set operation? ```d c[] ~= "lolno"; // fails, as [] isn't defined for Result. ``` Then what is the point of this type, if not to just make things difficult? It cannot be casted, and vector operations cannot be performed, and it seems to just serve as an unnecessary generalization.
Re: What is the 'Result' type even for?
On Friday, 20 January 2023 at 03:11:33 UTC, Ruby The Roobster wrote: Take this example: ```d import std; void main() { auto c = "a|b|c|d|e".splitter('|'); c.writeln; string[] e = ["a", "b", "c", "d", "e"]; assert(c.equal(e)); typeof(c).stringof.writeln; } ``` The program prints: ["a", "b", "c", "d", "e"] Result What is the purpose of this 'Result' type? To serve as a generic range? Because, it seems to only cause problems. For example, you cannot assign or cast the result type into a range, even when the type has the same inherent function: ```d string[] c = "a|b|c|d|e".splitter('|'); // fails string[] d = cast(string[])"a|b|c|d|e".splitter('|'); // also fails ``` And if you need to perform a set operation? ```d c[] ~= "lolno"; // fails, as [] isn't defined for Result. ``` Then what is the point of this type, if not to just make things difficult? It cannot be casted, and vector operations cannot be performed, and it seems to just serve as an unnecessary generalization. Furthermore, it can also be confirmed that each member of c is a string, further solidifying my opinion of 'Result' as just being a generic range template, that cannot be casted to an array of the original type.
Re: What is the 'Result' type even for?
On 1/19/23 10:11 PM, Ruby The Roobster wrote: Take this example: ```d import std; void main() { auto c = "a|b|c|d|e".splitter('|'); c.writeln; string[] e = ["a", "b", "c", "d", "e"]; assert(c.equal(e)); typeof(c).stringof.writeln; } ``` The program prints: ["a", "b", "c", "d", "e"] Result What is the purpose of this 'Result' type? To serve as a generic range? Because, it seems to only cause problems. For example, you cannot assign or cast the result type into a range, even when the type has the same inherent function: ```d string[] c = "a|b|c|d|e".splitter('|'); // fails string[] d = cast(string[])"a|b|c|d|e".splitter('|'); // also fails ``` The `Result` type is an internal type that provides a lazily-evaluated range of the original string. That is, no array is allocated, and the actual searching of the `|` character isn't done until you start fetching each element. It returns windows into the *original* string, and builds each window one element at a time (via slicing). So when you say "cast to a range", you are incorrectly stating the type `string[]` as a "range", when you are trying to cast to an array of strings. The `Result` type *is* a range, it's a range of strings. But it is not an array. If you want an array, you can use the `split` function, which allocates an array to hold all the slices. And if you need to perform a set operation? ```d c[] ~= "lolno"; // fails, as [] isn't defined for Result. ``` Right, because a `Result` is not an array. Appending is an array operation, not a range operation. Then what is the point of this type, if not to just make things difficult? It cannot be casted, and vector operations cannot be performed, and it seems to just serve as an unnecessary generalization. The point is to be a range over the original input, evaluated lazily. Using this building block, you can create an array, or use some other algorithm, or whatever you want. All without allocating more space to hold an array. -Steve
Re: What is the 'Result' type even for?
On Fri, Jan 20, 2023 at 03:11:33AM +, Ruby The Roobster via Digitalmars-d-learn wrote: > Take this example: > > ```d > import std; > void main() > { > auto c = "a|b|c|d|e".splitter('|'); > c.writeln; > string[] e = ["a", "b", "c", "d", "e"]; > assert(c.equal(e)); > typeof(c).stringof.writeln; > } > ``` > > The program prints: > > ["a", "b", "c", "d", "e"] > Result > > What is the purpose of this 'Result' type? To serve as a generic > range? It's a Voldemort type, representing a range that iterates over its elements lazily. > Because, it seems to only cause problems. For example, you cannot > assign or cast the result type into a range, even when the type has > the same inherent function: > > ```d > string[] c = "a|b|c|d|e".splitter('|'); // fails > string[] d = cast(string[])"a|b|c|d|e".splitter('|'); // also fails > ``` You're confusing arrays and ranges. A "range" isn't any specific type, it refers to *any* type that behaves a certain way (behaves like a range). Each `Result` you get back has its own unique type (arguably, it's a compiler bug to display it as merely `Result` without distinguishing it from other identically-named but distinct Voldemort types), so you cannot just assign it back to an array. You can either create an array from it using std.array.array, use a function that eagerly creates its results instead of a lazy result (in the above instance, use std.string.split instead of .splitter), or use std.algorithm.copy to copy the contents of the lazy range into an array: // Option 1 string[] c = "a|b|c|d|e".splitter('|').dup; // Option 2 string[] c = "a|b|c|d|e".split('|'); // Option 3 // Caveat: .copy expects you to have prepared the buffer // beforehand to be large enough to hold the contents; it does // not reallocate the result array for you. string[] result = new string[5]; "a|b|c|d|e".splitter('|').copy(result); [...] > Then what is the point of this type, if not to just make things > difficult? It cannot be casted, and vector operations cannot be > performed, and it seems to just serve as an unnecessary > generalization. It serves to chain further range operations into a pipeline: string[] c = "a|b|c|d|e".splitter('|') .filter!(c => c >= 'b' && c <= 'd') .map!(c => c+1) .array; Because ranges are lazily iterated, the .array line only allocates the 3 elements that got through the .filter. Whereas if you created the intermediate result array eagerly, you'd have to allocate space for 5 elements only to discard 2 of them afterwards. One way to think about this is that the intermediate Result ranges are like the middle part of a long pipe; you cannot get stuff from the middle of the pipe without breaking it, you need to terminate the pipe with a sink (like .array, .copy, etc.) first. T -- I am Pentium of Borg. Division is futile; you will be approximated.
Re: What is the 'Result' type even for?
On Friday, 20 January 2023 at 03:30:56 UTC, Steven Schveighoffer wrote: On 1/19/23 10:11 PM, Ruby The Roobster wrote: ... The point is to be a range over the original input, evaluated lazily. Using this building block, you can create an array, or use some other algorithm, or whatever you want. All without allocating more space to hold an array. -Steve I get the point that it is supposed to be lazy. But why are these basic cases not implemented? I shouldn't have to go write a wrapper for something as simple as casting this type to the original type. This is one of the things that one expects the standard library to do for you.
Re: What is the 'Result' type even for?
On Fri, Jan 20, 2023 at 03:34:43AM +, Ruby The Roobster via Digitalmars-d-learn wrote: > On Friday, 20 January 2023 at 03:30:56 UTC, Steven Schveighoffer wrote: > > On 1/19/23 10:11 PM, Ruby The Roobster wrote: > > ... > > > > The point is to be a range over the original input, evaluated > > lazily. Using this building block, you can create an array, or use > > some other algorithm, or whatever you want. All without allocating > > more space to hold an array. [...] > I get the point that it is supposed to be lazy. But why are these > basic cases not implemented? I shouldn't have to go write a wrapper > for something as simple as casting this type to the original type. > This is one of the things that one expects the standard library to do > for you. There's no need to write any wrappers. Just tack `.array` to the end of your pipeline, and you're good to go. T -- My father told me I wasn't at all afraid of hard work. I could lie down right next to it and go to sleep. -- Walter Bright
Re: What is the 'Result' type even for?
On 1/19/23 10:34 PM, Ruby The Roobster wrote: On Friday, 20 January 2023 at 03:30:56 UTC, Steven Schveighoffer wrote: On 1/19/23 10:11 PM, Ruby The Roobster wrote: ... The point is to be a range over the original input, evaluated lazily. Using this building block, you can create an array, or use some other algorithm, or whatever you want. All without allocating more space to hold an array. I get the point that it is supposed to be lazy. But why are these basic cases not implemented? I shouldn't have to go write a wrapper for something as simple as casting this type to the original type. This is one of the things that one expects the standard library to do for you. A range simply does not provide the API you are seeking. It provides 3 methods: front popFront empty That's it. It does not provide appending. If you want appending or random access, use an array: ```d auto c = "a|b|c|d|e".split('|'); static assert(is(typeof(c) == string[])); // or: auto c2 = "a|b|c|d|e".splitter('|').array; // convert range to an array ``` -Steve
Re: What is the 'Result' type even for?
On Friday, 20 January 2023 at 03:11:33 UTC, Ruby The Roobster wrote: Take this example: [...] What is the purpose of this 'Result' type? To serve as a generic range? Yes this is a lazy input range. Use `.array` to yield directly as a concrete value, then you can append using `~=`. Note that there are special functions to keep the laziness, e.g `chain` to happen an input range to another element-compatible input range ``` import std; void main() { auto c = "a|b|c|d|e".splitter("|").chain(["f"]); string[] e = ["a", "b", "c", "d", "e", "f"]; assert(c.equal(e)); } ```
Re: What is the 'Result' type even for?
On Friday, 20 January 2023 at 03:11:33 UTC, Ruby The Roobster wrote: What is the purpose of this 'Result' type? To serve as a generic range? Because, it seems to only cause problems... No. When I first started learning this language, I thought the same thing. However, such a designm consisting of structs was preferred because it is necessary for the ranges to be used as a whole (connected end-to-Znd like a pipe). qWalter has a very old [article](https://www.drdobbs.com/cpp/voldemort-types-in-d/232901591) about Voldemort which is the basis of D, pllease read it. D's ranges are very strong for those who know how to use it. SDB@79
Re: What is the 'Result' type even for?
On 1/19/23 19:11, Ruby The Roobster wrote: > typeof(c).stringof.writeln; > The program prints: > > ["a", "b", "c", "d", "e"] > Result > > What is the purpose of this 'Result' type? Just to make sure, 'Result' is what the programmer of a Phobos algorithm chose to name a struct type. It could be anything. I will try to demonstrate it by naming my struct 'MyResult' below. The following range algorithm alternates between the two values it is called with. The pragma(msg) inside 'main' prints MyResult. auto alternate(T)(T a, T b) { // This function will return an object // of the following nested struct. // (Note: This is for demonsration // purposes only. Yes, this can be // be more optimal.) struct MyResult { bool useB = false; enum empty = false; // Infinite T front() { return useB ? b : a; } void popFront() { // Flip the selector useB = !useB; } } // Here, an object of the struct is // returned. It has single member (useB) // that it uses as a selector. // The values 'a' and 'b' are the actual // function arguments. return MyResult(); } import std; void main() { auto myRange = alternate(42, 7); // This prints 'MyResult' at compile time pragma(msg, typeof(myRange)); const expected = [ 42, 7, 42, 7, 42 ]; assert(myRange.take(5).equal(expected)); } > even when the type > has the same inherent function: Different instantiations of templates are distinct types. For example, if I called 'alternate' with two 'long' values, both alternate!int (as instantiated by the code above) and alternate!long would have different MyResult struct types. Although they would have the same functionality, they would be compiled potentially with very different CPU instructions and would not be assignable. Ali
Re: What is the 'Result' type even for?
On Friday, 20 January 2023 at 04:46:07 UTC, Ali Çehreli wrote: Different instantiations of templates are distinct types. For example, if I called 'alternate' with two 'long' values, both alternate!int (as instantiated by the code above) and alternate!long would have different MyResult struct types. In general, the ranges are compatible with each other because they use the empty, front, popFront interface. In the example below, different types (one of which is double) but the same ranges can be combined with chain(). However, it is necessary to convert it to array because of the opCmp() compatibility from algorithms such as sorting. ```d import std.algorithm : sort; import std.conv : to; import std.range; import std.stdio; enum limit = 5; enum step = limit / 10.0;/* enum step = 1; //*/ void main() { TypeInfo rangeType; auto a = iota(limit); auto b = iota(step, limit, step); /* <- toggle comment, please add -> / auto ab = chain(a, b); rangeType = typeid(ab);/*/ auto arrA = a.array.to!(double[]); auto arrB = b.array; auto ab = chain(arrA, arrB); rangeType = typeid(ab.sort);//*/ ab.writeln(": ", rangeType); } /* current print: == [0, 0.5, 1, 1, 1.5, 2, 2, 2.5, 3, 3, 3.5, 4, 4, 4.5]: std.range.SortedRange!(std.range.chain!(double[], double[]).chain(double[], double[]).Result, "a < b", 0).SortedRange other print: [0, 1, 2, 3, 4, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5]: std.range.chain!(std.range.iota!(int, int).iota(int, int).Result, std.range.iota!(double, int, double).iota(double, int, double).Result).chain(std.range.iota!(int, int).iota(int, int).Result, std.range.iota!(double, int, double).iota(double, int, double).Result).Result ``` SDB@79
Re: What is the 'Result' type even for?
On Friday, 20 January 2023 at 03:39:48 UTC, H. S. Teoh wrote: On Fri, Jan 20, 2023 at 03:34:43AM +, Ruby The Roobster via Digitalmars-d-learn wrote: On Friday, 20 January 2023 at 03:30:56 UTC, Steven Schveighoffer wrote: > On 1/19/23 10:11 PM, Ruby The Roobster wrote: > ... > > The point is to be a range over the original input, > evaluated lazily. Using this building block, you can create > an array, or use some other algorithm, or whatever you want. > All without allocating more space to hold an array. [...] I get the point that it is supposed to be lazy. But why are these basic cases not implemented? I shouldn't have to go write a wrapper for something as simple as casting this type to the original type. This is one of the things that one expects the standard library to do for you. There's no need to write any wrappers. Just tack `.array` to the end of your pipeline, and you're good to go. T Thank you. I didn't know that there was such a property `.array`.
Re: What is the 'Result' type even for?
On Fri, Jan 20, 2023 at 12:49:54PM +, Ruby The Roobster via Digitalmars-d-learn wrote: [...] > Thank you. I didn't know that there was such a property `.array`. It's not a property, it's a Phobos function from std.array. T -- INTEL = Only half of "intelligence".