Thread overview
Choice ranges?
Mar 28, 2014
H. S. Teoh
Mar 28, 2014
Meta
Mar 28, 2014
bearophile
Mar 28, 2014
Ali Çehreli
Mar 29, 2014
Timon Gehr
March 28, 2014
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?


T

-- 
The fact that anyone still uses AOL shows that even the presence of options doesn't stop some people from picking the pessimal one. - Mike Ellis
March 28, 2014
On Friday, 28 March 2014 at 19:02:48 UTC, 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?
>
>
> T

You could try using std.variant.Algebraic. I've used it successfully before, but it's a bit clumsy and out of date.
March 28, 2014
H. S. Teoh:

> So how would I implement something like this?

One option is to wrap those ranges in classes. See std.range for the adapters. (I have not used them yet).

Bye,
bearophile
March 28, 2014
On 03/28/2014 01:46 PM, bearophile wrote:
> H. S. Teoh:
>
>> So how would I implement something like this?
>
> One option is to wrap those ranges in classes. See std.range for the
> adapters. (I have not used them yet).

Link:

  http://dlang.org/phobos/std_range.html#.inputRangeObject

Short examples here as well under "Run-time polymorphism with inputRangeObject() and outputRangeObject()":

  http://ddili.org/ders/d.en/ranges_more.html

<quote>
inputRangeObject() is flexible enough to support all of the non-output ranges: InputRange, ForwardRange, BidirectionalRange, and RandomAccessRange. Because of that flexibility, the object that it returns cannot be defined by auto.
</quote>

Ali

March 29, 2014
On 03/28/2014 08:00 PM, 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?
>
>
> T
>

The following is as close as I got. I think the definite initialization checks DMD implements are horribly broken for union fields.

import std.range, std.algorithm, std.typetuple, std.traits;

template CommonElementType(T...)if(allSatisfy!(isInputRange,T)){
    alias CommonElementType = CommonType!(staticMap!(ElementType,T));
}

private template Neg(alias a){
    enum Neg(T...)=!a!T;
}

struct SumRange(T...)if(allSatisfy!(isInputRange,T)&&!is(void==CommonElementType!T)&&allSatisfy!(Neg!hasElaborateDestructor,T)&&allSatisfy!(Neg!hasElaborateCopyConstructor,T)){
    size_t tag;
    private union{ T rs=void; }
    // private this();
    @property front()@trusted{
        switch(tag){
            foreach(i,_;T) case i: return rs[i].front;
            default: assert(0);
        }
    }
    @property bool empty()@trusted{
        switch(tag){
            foreach(i,_;T) case i: return rs[i].empty;
            default: assert(0);
        }
    }
    void popFront()@trusted{
        switch(tag){
            foreach(i,_;T) case i: return rs[i].popFront();
            default: assert(0);
        }
    }
}

private T buildSum(T, size_t tag,S)(S arg)@trusted{
    T r;
    r.tag=tag;
    r.rs[tag]=arg;
    return r;
}

auto inl(T,S)(S arg)if(!hasElaborateDestructor!S&&!hasElaborateDestructor!T&&!hasElaborateCopyConstructor!S&&!hasElaborateCopyConstructor!T){
    return buildSum!(SumRange!(S,T),0)(arg);
}
auto inr(S,T)(T arg)if(!hasElaborateDestructor!S&&!hasElaborateDestructor!T&&!hasElaborateCopyConstructor!S&&!hasElaborateCopyConstructor!T){
    return buildSum!(SumRange!(S,T),1)(arg);
}

auto f(R,S)(bool condition,R x,S y){
    auto r1(){
        return cartesianProduct(x, y).map!(a=>[a.expand])
            .joiner;
    }
    auto r2(){
        return cartesianProduct(x, y).map!(a=>[a.expand])
            .joiner
            .filter!(a=>a>2);
    }
    if(condition) return r1().inl!(typeof(r2()));
    else return r2().inr!(typeof(r1()));
}

void main(){
    import std.stdio;
    writeln(f(true, [1,2,3], [4,5,6]));
    writeln(f(false, [1,2,3], [4,5,6]));
}