Recommendations on avoiding range pipeline type hell

Chris Piker chris at hoopjump.com
Sat May 15 13:46:57 UTC 2021


On Saturday, 15 May 2021 at 11:51:11 UTC, Adam D. Ruppe wrote:
> On Saturday, 15 May 2021 at 11:25:10 UTC, Chris Piker wrote:
> The idea is you aren't supposed to care what the type is, just 
> what attributes it has, e.g., can be indexed, or can be 
> assigned, etc.

(Warning, new user rant ahead.  Eye rolling warranted and 
encouraged)

I'm trying to do that, but range3 and range2 are written by me 
not a Phobos wizard, and there's a whole library of template 
functions a person needs to learn to make their own pipelines.  
For example:
```d
// From std/range/package.d
CommonType!(staticMap!(ElementType, staticMap!(Unqual, Ranges))

alias RvalueElementType = CommonType!(staticMap!(.ElementType, 
R));
// ... what's with the . before the ElementType statement?  Line 
921 says
// .ElementType depends on RvalueElementType.  How can they 
depend on
// each other?  Is this a recursive template thing?
```

and all the other automagic stuff that phobos pulls off to make 
ranges work.  If that's what's needed to make a custom range 
type, then D ranges should come with the warning **don't try this 
at home**.  (Ali's book made it look so easy that I got sucker in)

Every time I slightly change the inputs to range2, then a 
function that operates on *range3* output types blows up with a 
helpful message similar to:
```
template 
das2.range.PrioritySelect!(PriorityRange!(DasRange!(Tuple!(int, 
int)[], int function(Tuple!(int, int)) pure nothrow @nogc @safe, 
int function(Tuple!(int, int)) pure nothrow @nogc @safe, 
Tuple!(int, int), int), int function() pure nothrow @nogc @safe), 
PriorityRange!(DasRange!(Tuple!(int, int)[], int 
function(Tuple!(int, int)) pure nothrow @nogc @safe, int 
function(Tuple!(int, int)) pure nothrow @nogc @safe, Tuple!(int, 
int), int), int function() pure nothrow @nogc 
@safe)).PrioritySelect.getReady.filter!((rng) => 
!rng.empty).filter cannot deduce function from argument types 
!()(PriorityRange!(DasRange!(Tuple!(int, int)[], int 
function(Tuple!(int, int)) pure nothrow @nogc @safe, int 
function(Tuple!(int, int)) pure nothrow @nogc @safe, Tuple!(int, 
int), int), int function() pure nothrow @nogc @safe), 
PriorityRange!(DasRange!(Tuple!(int, int)[], int 
function(Tuple!(int, int)) pure nothrow @nogc @safe, int 
function(Tuple!(int, int)) pure nothrow @nogc @safe, Tuple!(int, 
int), int), int function() pure nothrow @nogc @safe))
```
What the heck is that?

> Anyway, you put it all in one bit thing and this is kinda 
> important: avoid assigning it to anything. You'd ideally do all 
> the work, from creation to conclusion, all in the big pipeline.

I fell back to using assignments just to make sure range2 values 
were saved in a concrete variable so that range3 didn't break 
when I changed the lambda that was run by range2 to mutate it's 
output elements.

What went in to getting the element to range3's doorstep is a 
detail that I shouldn't have to care about inside range3 code, 
but am forced to care about it, because changing range2's type, 
changes range3's type and triggers really obscure error messages. 
(Using interfaces or *gasp* casts, would break the TMI situation.)

> So say you want to write it
>
> auto mega_range = range1.range2!(lambda2).range3!(lambda3);
> writeln(mega_range);
>
> that'd prolly work, writeln is itself flexible enough, but 
> you'd prolly be better off doing like

Sure it will work, because writeln isn't some function written by 
a new user, it's got all the meta magic.

> This way the concrete type never enters into things, it is all 
> just a detail the compiler tracks to ensure the next consumer 
> doesn't try to do things the previous step does not support.

It's all just a detail the compiler tracks, until you're not 
sending to writeln, but to your own data consumer.  Then, you'd 
better know all of std.traits and std.meta cause you're going to 
need them too implement a range-of-ranges consumer.  And by the 
way you have to use a range of ranges instead of an array of 
ranges because two ranges that look to be identical types, 
actually are not identical types and so can't go into the same 
array.

Here's an actual (though formatted by me) error message I got 
stating that two things were different and thus couldn't share an 
array.  Can you see the difference?  I can't.  Please point it 
out if you do.

```d
das2/range.d(570,39): Error: incompatible types for (dr_fine) : 
(dr_coarse):

das2.range.PriorityRange!(
   DasRange!(
     Take!(
       ZipShortest!(
         cast(Flag)false, Result, Generator!(function () @safe => 
uniform(0, 128))
       )
     ),
     int function(Tuple!(int, int)) pure nothrow @nogc @safe,
     int function(Tuple!(int, int)) pure nothrow @nogc @safe,
     Tuple!(int, int),
     int
   ),
   int function() pure nothrow @nogc @safe
)

and

das2.range.PriorityRange!(
   DasRange!(
     Take!(
       ZipShortest!(
         cast(Flag)false, Result, Generator!(function () @safe => 
uniform(0, 128))
       )
     ),
     int function(Tuple!(int, int)) pure nothrow @nogc @safe,
     int function(Tuple!(int, int)) pure nothrow @nogc @safe,
     Tuple!(int, int),
     int
   ),
   int function() pure nothrow @nogc @safe
)
```

>> But, loops are bad.  On the D blog I've seen knowledgeable 
>> people say all loops are bugs.
>
> Meh, don't listen to that nonsense, just write what works for 
> you. D's strength is that it adapts to different styles and 
> meets you where you are. Listening to dogmatic sermons about 
> idiomatic one true ways is throwing that strength away and 
> likely to kill your personal productivity as you're fighting 
> your instincts instead of making it work.

Insightful.

Anyway, if you made it this far, you're a saint.  Thanks for your 
time :)





More information about the Digitalmars-d-learn mailing list