Thread overview
Alternative to std.range.choose
Jul 22, 2020
James Gray
Jul 22, 2020
WebFreak001
Jul 22, 2020
James Gray
July 22, 2020
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
}
July 22, 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);
    }
July 22, 2020
On Wednesday, 22 July 2020 at 06:16:44 UTC, WebFreak001 wrote:
> On Wednesday, 22 July 2020 at 04:33:20 UTC, James Gray wrote:
>> [...]
>
> 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.
>
> [...]

Thank you very much.
July 22, 2020
On 7/22/20 12:33 AM, 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
> }

I know this is a contrived example, but choose is not good for such a thing. It should only be used if the range types are different.

g could be:

auto g(ulong n) {
  return only(n != 0L ? 100/n : 0)
}

But in any case, changing choose to use lazy is probably a good answer.

-Steve