Alternative to std.range.choose

WebFreak001 d.forum at webfreak.org
Wed Jul 22 06:16:44 UTC 2020


On Wednesday, 22 July 2020 at 04:33:20 UTC, James Gray wrote:
> Is there a better way to achieve behaviour similar to 
> rangeFuncIf
> below? f gives a contrived example of when one might want this. 
> g is
> how one might try and achieve the same with std.range.choose.
>
> import std.stdio;
> import std.range : only, chain, join, choose;
> import std.algorithm : map;
>
> auto rangeFunctIf(alias F1, alias F2)(bool c)
>  if ( __traits(compiles,F1().chain(F2())))
> {
>    return only(true).repeat(c?1:0).map!(x=>F1()).join
>       .chain(only(true).repeat(c?0:1).map!(x=>F2()).join);
> }
>
> auto f(ulong n) {
>  return (n!=0uL).rangeFuncIf!(()=>only(100/n), ()=>only(0));
> }
> auto g(ulong n) {
>  return choose(n!=0uL,only(100/n),only(0));
> }
>
> void main()
> {
>  writeln(f(2));
>  writeln(f(0));
>  writeln(g(2));
>  //writeln(g(0)); <---- runtime error
> }

it seems `choose` evaluates both arguments instead of using lazy 
evaluation. IMO this is a broken API to me but it has been like 
this for longer so this would be difficult to change. 
Additionally with regards to storing in memory this is another 
problem.

However I think using .init this is solvable, so here is an 
alternative choose function which you can just use as drop-in 
replacement:

     import std.traits : Unqual;
     auto choose(R1, R2)(bool condition, return scope lazy R1 r1, 
return scope lazy R2 r2)
     if (isInputRange!(Unqual!R1) && isInputRange!(Unqual!R2) &&
         !is(CommonType!(ElementType!(Unqual!R1), 
ElementType!(Unqual!R2)) == void))
     {
         alias ChooseResult = __traits(getMember, std.range, 
"ChooseResult");
         return ChooseResult!(R1, R2)(condition,
                                      condition ? r1 : R1.init,
                                      condition ? R2.init : r2);
     }

The parameters are lazy so they are only evaluated once accessed, 
the ChooseResult can only store non-lazy parameters so we pass in 
dummy (.init) parameters there which are hopefully never used.

The `alias ChooseResult = __traits(getMember, std.range, 
"ChooseResult");` is needed because ChooseResult is private, but 
we want to access it anyway.

A little bit more instantiation heavy but effectively the same 
effect, though a little more stable API usage would be:

     auto choose(R1, R2)(bool condition, return scope lazy R1 r1, 
return scope lazy R2 r2)
     if (isInputRange!(Unqual!R1) && isInputRange!(Unqual!R2) &&
         !is(CommonType!(ElementType!(Unqual!R1), 
ElementType!(Unqual!R2)) == void))
     {
         import std.range : choose;

         return choose(condition,
                       condition ? r1 : R1.init,
                       condition ? R2.init : r2);
     }


More information about the Digitalmars-d-learn mailing list