www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Alternative to std.range.choose

reply James Gray <test test.com> writes:
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
}
Jul 21 2020
next sibling parent reply WebFreak001 <d.forum webfreak.org> writes:
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); }
Jul 21 2020
parent James Gray <test test.com> writes:
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.
Jul 22 2020
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
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
Jul 22 2020