Choice ranges?

Artur Skawina art.08.09 at gmail.com
Fri Mar 28 17:37:42 PDT 2014


On 03/28/14 23:10, H. S. Teoh wrote:
> On Fri, Mar 28, 2014 at 10:44:11PM +0100, Artur Skawina wrote:
>> On 03/28/14 20:00, H. S. Teoh wrote:
>>> Today I ran into an interesting situation where I have a function f
>>> that needs to return ranges of different types (but identical
>>> element types):
>>>
>>> 	auto f(A...)(A args) {
>>> 		...
>>> 		if (someCondition)
>>> 			return cartesianProduct(x, y)
>>> 				.joiner;
>>> 		else
>>> 			return cartesianProduct(x, y)
>>> 				.joiner
>>> 				.filter!someFilter;
>>> 	}
>>>
>>> This obviously can't compile, because the return types are not the
>>> same.  (Note that someCondition is only known at runtime.) But
>>> abstractly speaking, it *should* work, because the element type of
>>> the returned range is identical.
>>>
>>> So how would I implement something like this?
>>
>> eg
>>  	auto f(A...)(A args) {
>>  		...
>>  		return cartesianProduct(x, y)
>>  			.joiner
>>  			.filter!(a=>somecondition?true:somefilter(a));
>>  	}
> [...]
> 
> It's not that simple, though.  I guess I gave a bad example. In the
> actual code, someCondition decides whether or not a cartesian product is
> even used. So the return range needs to somehow switch between a
> cartesianProduct filtered by some filters, vs. an alternative algorithm
> filtered by some other filters.

D is statically typed -- there is no way for one function to return
different types that are selected by a runtime condition.
The fact that the produced elements have the same type is irrelevant.

You can wrap the range in a common one. When the above approach isn't
enough, there's always the next level:

   struct DynRange(R, F...) {
      R r;
      /*immutable*/ size_t n;

      static _ugh() { string r; foreach (N, _; F) r~="typeof(F["~N.stringof~"](r)) f"~N.stringof~";"; return r; }
      union U { mixin(_ugh()); }
      U u;

      this(R r, size_t n) {
         this.r = r; this.n = n;
         foreach (N, _; typeof(F)) if (N==n) u.tupleof[N] = F[N](r);
      }

      auto ref front() @property { foreach (N, _; typeof(F)) if (N==n) return u.tupleof[N].front; assert(0); }
      bool empty() @property     { foreach (N, _; typeof(F)) if (N==n) return u.tupleof[N].empty; assert(0); }
      void popFront()            { foreach (N, _; typeof(F)) if (N==n) u.tupleof[N].popFront(); }

   }

   import std.array;

   auto f(R)(R r, bool somecondition) {
      import std.algorithm, std.conv;
      return DynRange!(R,
            map!"a*a",
            r=>filter!"a%2"((map!(a=>to!string(a^^a).length)(r)))
         )(r, somecondition);
   }

   void main() {
      import std.stdio;

      auto r = [1,2,3,4,5].f(false);
      writeln(r);
      r = [1,2,3,4,5].f(true);
      writeln(r);
   }

[Optimizing DynRange is left as an exercise for the reader. ;) In practice,
 when used with just two chains, the gain would be minimal anyway. Adding
 a level of indirection for the range primitives would prevent inlining.] 

artur


More information about the Digitalmars-d-learn mailing list