www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - isInputRange not satisfied even if all individual conditions are

reply Johannes Loher <johannes.loher fg4f.de> writes:
Recently, I read about problems regarding the API design of ranges (not
being able to make them const, differences between class and struct
based ranges etc.).

One of the issues that came up what the fact that sometimes it is useful
to use the class wrappers `InputRangeObject` etc. if you need to decide
at runtime which range you want to use. I ran into this into a personal
project in the past and this also was how I solved the issue.

Another suggestion in that thread was to simply use choose, which I
guess should work, but I had trouble getting it to work in my particular
case.

However, a third option came to my mind: Why not simply use a sumtype to
return either one range or the other? In order to make it more
convenient to use afterwards, it would be useful if we could regard a
sumtype of ranges as a range itself. That should be no problem because
we just need to delegate all function calls to the range api to the
actual range that sits inside the sumtype.

So I tried to implement this and it kind of seems to work (I can call
the individual functions on the sumtype using UFCS) but for some reason,
`isInputRange` evaluates to `false`. What makes it even weirder is the
fact that all individual conditions that are part of `isInputRange` seem
to be fulfilled. Could somebody explain to me what is going on? Here is
the code:

https://run.dlang.io/gist/93bcc196f73b0a7c7aa1851beef59c22 (currently
give me a 502 though)

```

/+ dub.sdl:
	name "sumtype-range"
    dependency "sumtype" version="~>0.9.4"
+/

module sumtype_range;

import std.meta : allSatisfy, staticMap;
import std.traits : allSameType;
import std.range : isInputRange, ElementType, empty;

import sumtype;

private template EmptyLambda(T) if (isInputRange!T)
{
    alias EmptyLambda = (ref T t) => t.empty;
}

 property bool empty(TypeArgs...)(auto ref scope SumType!(TypeArgs) r)
        if (allSatisfy!(isInputRange, TypeArgs) && TypeArgs.length > 0)
{
    return r.match!(staticMap!(EmptyLambda, TypeArgs));
}

private template FrontLambda(T) if (isInputRange!T)
{
    alias FrontLambda = (ref T t) => t.front;
}

 property ElementType!(TypeArgs[0]) front(TypeArgs...)(auto ref scope
SumType!(TypeArgs) r)
        if (allSatisfy!(isInputRange, TypeArgs)
            && allSameType!(staticMap!(ElementType, TypeArgs)) &&
TypeArgs.length > 0)
{
    return r.match!(staticMap!(FrontLambda, TypeArgs));
}

private template PopFrontLambda(T) if (isInputRange!T)
{
    alias PopFrontLambda = (ref T t) => t.popFront();
}

void popFront(TypeArgs...)(auto ref scope SumType!(TypeArgs) r)
        if (allSatisfy!(isInputRange, TypeArgs) && TypeArgs.length > 0)
{
    return r.match!(staticMap!(PopFrontLambda, TypeArgs));
}

enum bool myIsInputRange(R) =
    is(typeof(R.init) == R)
    && is(ReturnType!((R r) => r.empty) == bool)
    && is(typeof((return ref R r) => r.front))
    && !is(ReturnType!((R r) => r.front) == void)
    && is(typeof((R r) => r.popFront));

void main() {
	import std.range : iota, only;
    import std.stdio : writeln;
    import std.traits : ReturnType;

    auto i = iota(4);
    auto o = only(1);
    alias R = SumType!(typeof(i), typeof(o));

    // all individual conditions of `isInputRange` are satisfied
    static assert(is(typeof(R.init) == R));
    static assert(is(ReturnType!((R r) => r.empty) == bool));
    static assert(is(typeof((return ref R r) => r.front)));
    static assert(!is(ReturnType!((R r) => r.front) == void));
    static assert(is(typeof((R r) => r.popFront)));

    // but `isInputRange` is not satisfied
    static assert(!isInputRange!(R));

    // and neither is a local copy
    static assert(!myIsInputRange!(R));
}
```
Jun 26 2020
parent reply ag0aep6g <anonymous example.com> writes:
On 26.06.20 15:09, Johannes Loher wrote:
 import std.meta : allSatisfy, staticMap;
 import std.traits : allSameType;
 import std.range : isInputRange, ElementType, empty;
[...]
  property bool empty(TypeArgs...)(auto ref scope SumType!(TypeArgs) r)
          if (allSatisfy!(isInputRange, TypeArgs) && TypeArgs.length > 0)
 {
      return r.match!(staticMap!(EmptyLambda, TypeArgs));
 }
[...]
 enum bool myIsInputRange(R) =
      is(typeof(R.init) == R)
      && is(ReturnType!((R r) => r.empty) == bool)
      && is(typeof((return ref R r) => r.front))
      && !is(ReturnType!((R r) => r.front) == void)
      && is(typeof((R r) => r.popFront));
 
 void main() {
[...]
      import std.traits : ReturnType;
[...]
      alias R = SumType!(typeof(i), typeof(o));
 
      // all individual conditions of `isInputRange` are satisfied
      static assert(is(typeof(R.init) == R));
      static assert(is(ReturnType!((R r) => r.empty) == bool));
[...]
      // but `isInputRange` is not satisfied
      static assert(!isInputRange!(R));
 
      // and neither is a local copy
      static assert(!myIsInputRange!(R));
 }
`isInputRange!R` fails because it has no knowledge of your free `empty` function. Without `empty`, `R` is obviously not a range. `myIsInputRange!R` fails because you forgot to import `ReturnType` in module scope. You're importing it locally in `main`, so the check passes there.
Jun 26 2020
next sibling parent reply Johannes Loher <johannes.loher fg4f.de> writes:
Am 26.06.20 um 15:35 schrieb ag0aep6g:
 `isInputRange!R` fails because it has no knowledge of your free `empty`
 function. Without `empty`, `R` is obviously not a range.
Ah, OK, that makes sense. It's kind of sad though because it really limits the extensibility of existing types with UFCS. Do you know if there is a way around this?
Jun 26 2020
parent Paul Backus <snarwin gmail.com> writes:
On Friday, 26 June 2020 at 13:53:46 UTC, Johannes Loher wrote:
 Am 26.06.20 um 15:35 schrieb ag0aep6g:
 `isInputRange!R` fails because it has no knowledge of your 
 free `empty` function. Without `empty`, `R` is obviously not a 
 range.
Ah, OK, that makes sense. It's kind of sad though because it really limits the extensibility of existing types with UFCS. Do you know if there is a way around this?
You can use my recently-released Dub package `addle` to make your UFCS extensions visible to std.range. Dub: https://code.dlang.org/packages/addle Announcement: https://forum.dlang.org/thread/yruztugitygumwcsmjkc forum.dlang.org
Jun 26 2020
prev sibling parent ag0aep6g <anonymous example.com> writes:
On 26.06.20 15:35, ag0aep6g wrote:
 `isInputRange!R` fails because it has no knowledge of your free `empty` 
 function. Without `empty`, `R` is obviously not a range.
To be clear: It's the same with `front` and `popFront`. You can't implement any range primitives as free functions. It only works for arrays because those implementations are part of std.range.
Jun 26 2020