Jump to page: 1 2
Thread overview
January 20

Take this example:

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:

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?

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.

January 20

On Friday, 20 January 2023 at 03:11:33 UTC, Ruby The Roobster wrote:

>

Take this example:

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:

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?

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.

January 19

On 1/19/23 10:11 PM, Ruby The Roobster wrote:

>

Take this example:

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:

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?

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

January 19
On Fri, Jan 20, 2023 at 03:11:33AM +0000, 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.
January 20

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.

January 19

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:

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

January 19
On Fri, Jan 20, 2023 at 03:34:43AM +0000, 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
January 20

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));
}
January 20

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 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

January 19
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

« First   ‹ Prev
1 2