Jump to page: 1 2
Thread overview
wrong isInputRange design
Dec 03, 2016
rumbu
Dec 03, 2016
Jerry
Dec 03, 2016
Nick Treleaven
Dec 03, 2016
rumbu
Dec 04, 2016
lobo
Dec 04, 2016
rumbu
Dec 04, 2016
Mike Parker
Dec 04, 2016
rumbu
Dec 04, 2016
ag0aep6g
Dec 04, 2016
Jonathan M Davis
Dec 05, 2016
pineapple
Dec 06, 2016
jmh530
Dec 06, 2016
rumbu
December 03, 2016
import std.range.primitives: isInputRange;

void test(R)(ref R range) if (isInputRange!R)
{
    auto c = r.front; //Error: no property 'front' for type 'string'	
}

string s = "some string";
test(s);

The problem is that isInputRange will always return true for string types (because it will check the availability of front, popFront, empty through the entire std.range.primitives module.

Since the only thing that was imported is std.range.primitives: isInputRange, front is not available for strings.

OK, the simple resolution is to import entirely std.range.primitives, but the source of the problem was another: I tried to define two overloads, one accepting strings, and one accepting ranges;

import std.range.primitives: isInputRange, ElementType;
import std.traits: isSomeChar;

void foo(C)(const(C)[] array) if (isSomeChar!C)
{
    //this will be never called	
}

void foo(R)(ref R range) if (isInputRange!R && isSomeChar!(ElementType!R))
{
    //this will be always called
    //any call to range.front, range.empty and so on will result in error
}

foo(somestring)

I expect foo!string to be called instead of foo!Range, because in my context isInputRange!string should return false. Instead, my context is hijacked by the definitions spread along std.range.primitives module.

The workaround I found is to define the second overload like this:

void foo(R)(ref R range) if (isInputRange!R && !isSomeString!R && isSomeChar!(ElementType!R))







December 03, 2016
On Saturday, 3 December 2016 at 11:52:00 UTC, rumbu wrote:
> import std.range.primitives: isInputRange;
>
> void test(R)(ref R range) if (isInputRange!R)
> {
>     auto c = r.front; //Error: no property 'front' for type 'string'	
> }
>
> string s = "some string";
> test(s);
>
> The problem is that isInputRange will always return true for string types (because it will check the availability of front, popFront, empty through the entire std.range.primitives module.
>
> Since the only thing that was imported is std.range.primitives: isInputRange, front is not available for strings.
>
> OK, the simple resolution is to import entirely std.range.primitives, but the source of the problem was another: I tried to define two overloads, one accepting strings, and one accepting ranges;
>
> import std.range.primitives: isInputRange, ElementType;
> import std.traits: isSomeChar;
>
> void foo(C)(const(C)[] array) if (isSomeChar!C)
> {
>     //this will be never called	
> }
>
> void foo(R)(ref R range) if (isInputRange!R && isSomeChar!(ElementType!R))
> {
>     //this will be always called
>     //any call to range.front, range.empty and so on will result in error
> }
>
> foo(somestring)
>
> I expect foo!string to be called instead of foo!Range, because in my context isInputRange!string should return false. Instead, my context is hijacked by the definitions spread along std.range.primitives module.
>
> The workaround I found is to define the second overload like this:
>
> void foo(R)(ref R range) if (isInputRange!R && !isSomeString!R && isSomeChar!(ElementType!R))

Is that the exact code? isInputRange checks to see if the type has "front" defined.

https://github.com/dlang/phobos/blob/v2.072.0/std/range/primitives.d#L162

Also "string" is just an alias of an array, "immutable(char)[]". So an array should have "front" defined. Can you post more code?
December 03, 2016
On Saturday, 3 December 2016 at 16:37:21 UTC, Jerry wrote:
> Also "string" is just an alias of an array, "immutable(char)[]". So an array should have "front" defined. Can you post more code?

You still have to `import std.range.primitives : front`, even for arrays.
December 03, 2016
On Saturday, 3 December 2016 at 16:37:21 UTC, Jerry wrote:
> On Saturday, 3 December 2016 at 11:52:00 UTC, rumbu wrote:
>> import std.range.primitives: isInputRange;
>>
>> void test(R)(ref R range) if (isInputRange!R)
>> {
>>     auto c = r.front; //Error: no property 'front' for type 'string'	
>> }
>>
[...]
>>
>> import std.range.primitives: isInputRange, ElementType;
>> import std.traits: isSomeChar;
>>
>> void foo(C)(const(C)[] array) if (isSomeChar!C)
>> {
>>     //this will be never called	
>> }
>>
>> void foo(R)(ref R range) if (isInputRange!R && isSomeChar!(ElementType!R))
>> {
>>     //this will be always called
>>     //any call to range.front, range.empty and so on will result in error
>> }
>>
>> foo(somestring)
>>
>> I expect foo!string to be called instead of foo!Range, because in my context isInputRange!string should return false. Instead, my context is hijacked by the definitions spread along std.range.primitives module.
>>
>> The workaround I found is to define the second overload like this:
>>
>> void foo(R)(ref R range) if (isInputRange!R && !isSomeString!R && isSomeChar!(ElementType!R))
>
> Is that the exact code? isInputRange checks to see if the type has "front" defined.

>
> https://github.com/dlang/phobos/blob/v2.072.0/std/range/primitives.d#L162
>
> Also "string" is just an alias of an array, "immutable(char)[]". So an array should have "front" defined. Can you post more code?

No, an array should not have "front" defined according to D language specification. "front" for arrays is defined in std.range.primitives. That's the problem, I cannot have two specialized functions (one taking arrays and one taking ranges), because std.range.primitives hijacks any char array and transforms it in a range). front, empty, popFront must not be defined for arrays in the same module as isInputRange.

Exact code is irrelevant, but you're welcome:

string constructor: https://github.com/rumbu13/numerics/blob/master/src/numerics/fixed.d#L978

range constructor
https://github.com/rumbu13/numerics/blob/master/src/numerics/fixed.d#L1104


December 04, 2016
On Saturday, 3 December 2016 at 17:29:47 UTC, rumbu wrote:
> On Saturday, 3 December 2016 at 16:37:21 UTC, Jerry wrote:
>> On Saturday, 3 December 2016 at 11:52:00 UTC, rumbu wrote:
>>>[...]
> [...]
>>> [...]
>>
>> Is that the exact code? isInputRange checks to see if the type has "front" defined.
>
>>
>> https://github.com/dlang/phobos/blob/v2.072.0/std/range/primitives.d#L162
>>
>> Also "string" is just an alias of an array, "immutable(char)[]". So an array should have "front" defined. Can you post more code?
>
> No, an array should not have "front" defined according to D language specification. "front" for arrays is defined in std.range.primitives. That's the problem, I cannot have two specialized functions (one taking arrays and one taking ranges), because std.range.primitives hijacks any char array and transforms it in a range). front, empty, popFront must not be defined for arrays in the same module as isInputRange.
>
> Exact code is irrelevant, but you're welcome:
>
> string constructor: https://github.com/rumbu13/numerics/blob/master/src/numerics/fixed.d#L978
>
> range constructor
> https://github.com/rumbu13/numerics/blob/master/src/numerics/fixed.d#L1104

This works for me when specialising for input ranges, strings and arrays.

auto f(T)(T val)
    if(isInputRange!T && !isSomeString!T && !isArray!T) {}

auto f(T)(T val)
    if(isSomeString!T) {}

auto f(T)(T val)
    if(isArray!T && !isSomeString!T)

bye,
lobo
December 04, 2016
On Sunday, 4 December 2016 at 05:31:59 UTC, lobo wrote:
>
> This works for me when specialising for input ranges, strings and arrays.
>
> auto f(T)(T val)
>     if(isInputRange!T && !isSomeString!T && !isArray!T) {}
>
> auto f(T)(T val)
>     if(isSomeString!T) {}
>
> auto f(T)(T val)
>     if(isArray!T && !isSomeString!T)
>
> bye,
> lobo

Yes, this is the same workaround I found, but that does not solve the fact that the following code does not compile:

import std.range.primitives : isInputRange;
void f(T)(T val) if (isInputRange!T)
{
  while (!val.empty) { auto c = val.front; val.popFront(); }
}

string s = "some string";
f(s);

Of course, the previous code will compile if we change the imports:
import std.range.primitives: isInputRange, front, popFront, empty;

But that just prove the bad design of isInputRange which cannot be used all alone without the rest of array UFCSs imported.

December 04, 2016
On Sunday, 4 December 2016 at 11:18:56 UTC, rumbu wrote:


>
> Of course, the previous code will compile if we change the imports:
> import std.range.primitives: isInputRange, front, popFront, empty;
>
> But that just prove the bad design of isInputRange which cannot be used all alone without the rest of array UFCSs imported.

isInputRange can be used alone just fine. But you aren't using it alone. You're using it together with the range primitives for arrays, but you are explicitly excluding them from the import by only selectively importing isInputRange. The import is working exactly as advertised. What is it about this case that makes you think it should behave differently?
December 04, 2016
On Sunday, 4 December 2016 at 11:50:08 UTC, Mike Parker wrote:
> On Sunday, 4 December 2016 at 11:18:56 UTC, rumbu wrote:
>
>
>>
>> Of course, the previous code will compile if we change the imports:
>> import std.range.primitives: isInputRange, front, popFront, empty;
>>
>> But that just prove the bad design of isInputRange which cannot be used all alone without the rest of array UFCSs imported.
>
> isInputRange can be used alone just fine. But you aren't using it alone.

 I'm using it all alone:

auto first(R)(R range) if (isInputRange!R)
{
  return range.front;
  //compile time error when calling first!string
}

auto first(C)(const(C)[] str)
{
   return str[0];
   //never called;
}

first(someString);

> You're using it together with the range primitives for arrays, but you are explicitly excluding them from the import by only selectively importing isInputRange. The import is working exactly as advertised. What is it about this case that makes you think it should behave differently?

Advertising from the docs says this: "An input range must define the primitives empty, popFront, and front." In my module context, I didn't define any of them, isInputRange!string must return false, because string is not a range, I repeat - *in my module context*. There is nowhere in the documentation where arrays are advertised as  ranges by default.

If I copy the isInputRange definition in my module, it will clearly return false:

import std.range.primitives: isInputRange;
template isInputRange2(R)
{
    enum bool isInputRange2 = is(typeof(
    (inout int = 0)
    {
        R r = R.init;     // can define a range object
        if (r.empty) {}   // can test for empty
        r.popFront();     // can invoke popFront()
        auto h = r.front; // can get the front of the range
    }));
}


static assert(isInputRange!string); //wrong!
static assert(!isInputRange2!string); //correct!

I think that the array range UFCSs must be moved out from the std.range.primitives and let the library user to decide if there is a need for range semantics applied to all arrays.

Otherwise, as long as you want array specializations for your functions, you must decorate all the range specializations with (isInputRange!T && !isArray!T). And you are compelled to use "f(T)(T t) if isArray!T" for all your array specializations instead of "f(T)(T[] x)".







December 04, 2016
On Sunday, 4 December 2016 at 13:37:35 UTC, rumbu wrote:
> There is nowhere in the documentation where arrays are advertised as  ranges by default.

Huh. It really doesn't seem to say so in the prose. It's shown in the examples for isInputRange, though:

    static assert( isInputRange!(int[]));
    static assert( isInputRange!(char[]));

> I think that the array range UFCSs must be moved out from the std.range.primitives and let the library user to decide if there is a need for range semantics applied to all arrays.

I'm afraid that's not so easy. std.range.primitives.isInputRange cannot see the range primitives when you import/define them yourself. You'd have to make isInputRange a mixin template, or replace the UFCS primitives with a wrapper type, or something like that. Either way, it'd be a major breaking change.

If you're passionate about this, I suggest you try and find a solution with an acceptable migration path. Work out all the annoying little details, and present it as a fleshed-out proposal. I wouldn't bet on it getting accepted, though. The current situation may be slightly irritating, but it may be the lesser evil when compared to breaking everyone's code by messing with arrays as ranges.

> Otherwise, as long as you want array specializations for your functions, you must decorate all the range specializations with (isInputRange!T && !isArray!T). And you are compelled to use "f(T)(T t) if isArray!T" for all your array specializations instead of "f(T)(T[] x)".

You can also use a "specialization" (in the narrower sense of the word that the spec uses):

    import std.range: isInputRange;
    import std.stdio: writeln;
    void f(R)(R r) if (isInputRange!R) { writeln("range"); }
    void f(A : E[], E)(A a) { writeln("array"); }
    void main() { int[] a; f(a); } /* calls the second overload; prints "array" */

See https://dlang.org/spec/template.html#parameters_specialization
December 04, 2016
On Sunday, December 04, 2016 13:37:35 rumbu via Digitalmars-d wrote:
> I think that the array range UFCSs must be moved out from the std.range.primitives and let the library user to decide if there is a need for range semantics applied to all arrays.

That's not happening. In fact, pretty much the only reason that the array range primitives haven't been moved to object.d is because of the auto-decoding issues with strings and the fact that the auto-decoding relies on std.utf in Phobos, whereas object.d is in druntime, which doesn't have access to Phobos. The fact that we currently have to import std.range or std.range.primitives to get arrays to behave properly as ranges is an annoyance that we want to fix, not something that we want to solidify.  And other modules - such as std.algorithm - actually publicly import std.range in an attempt to reduce the amount of importing pain with the range primitives. Ranges are a core part of D, and it was never really intended for the range primitives to be optional. They just didn't need to be put into the language or druntime to work, so Andrei put them in Phobos. There are some minor changes that were made to the language (e.g. foreach knowing about the input range primitives), but overall, the range stuff was done via the standard library, because there was no need to do it with the language, and Andrei favors doing stuff with the standard library where possible rather than doing it with the language.

So, really, the intention here is that all range based code imports the range primitives for arrays, and there is no plan to support the range-based stuff without them. And there was recently talk of putting the array range primitives in object.d in spite of the auto-decoding mess and just putting more of the std.utf stuff in druntime. So, if anything, that's the direction that things are going, not towards trying to make it possible for code to use isInputRange sanely without the array primitives. The only reason that you're even hitting this issue is because you're explicitly avoiding importing all of the range primitives together and are trying to grab certain ones individually, which was never an intended use case. So, if you're doing that and running into problems, I'm sorry, but you're trying to use the library in a way that it was not designed to be used.

- Jonathan M Davis

« First   ‹ Prev
1 2