July 29, 2012
On 07/29/2012 08:32 AM, Simen Kjaeraas wrote:
> On Sat, 28 Jul 2012 22:47:01 +0200, Chad J
> <chadjoan@__spam.is.bad__gmail.com> wrote:
>
>
>> isInputRange!___ r2 = [1,2,3].some.complex.expression();
>>
>> It doesn't make sense. isInputRange!() isn't a type, so how do I
>> constrain what type is returned from some arbitrary expression?
>>
>> So far the only way I know how to do this is to pass it through a
>> template and use template constraints:
>>
>> auto makeSureItsARange(T)(T arg) if ( isInputRange!T )
>> {
>> return arg;
>> }
>>
>> auto r2 = makeSureItsARange([1,2,3].some.complex.expression());
>>
>> however, the above is unreasonably verbose and subject to naming whimsy.
>
>
> import std.typetuple : allSatisfy;
>
> template checkConstraint( T ) {
> template checkConstraint( alias Constraint ) {
> enum checkConstraint = Constraint!T;
> }
> }
>
>
> template constrain( T... ) {
> auto constrain( U, string file = __FILE__, int line = __LINE__ )( auto
> ref U value ) {
> static assert( allSatisfy!( checkConstraint!U, T ), "Type " ~ U.stringof
> ~ " does not fulfill the constraints " ~ T.stringof );
> return value;
> }
> }
>
> version (unittest) {
> import std.range : isInputRange, ElementType;
>
> template hasElementType( T ) {
> template hasElementType( U ) {
> enum hasElementType = is( ElementType!U == T );
> }
> }
> }
> unittest {
> assert( __traits( compiles, { int[] a = constrain!isInputRange( [1,2,3]
> ); } ) );
> assert( !__traits( compiles, { int a = constrain!isInputRange( 2 ); } ) );
> assert( __traits( compiles, { int[] a = constrain!(isInputRange,
> hasElementType!int)( [1,2,3] ); } ) );
> assert( __traits( compiles, { string a = constrain!(isInputRange,
> hasElementType!dchar)( "abc" ); } ) );
> assert( !__traits( compiles, { string a = constrain!(isInputRange,
> hasElementType!dchar)( "abc"w ); } ) );
> }
>
>
> So there. Now, you simply use auto a = constrain!isInputRange(
> expression );. Is this what you wanted?
>

That's pretty good.  It's still not as concise or easy to discover as the language's natural syntax for type declarations, but it's the kind of thing I'd use for my own purposes as a trick to get around language limitations.
July 29, 2012
On 07/29/2012 11:54 AM, Ali Çehreli wrote:
> On 07/28/2012 01:47 PM, Chad J wrote:
>
>  > What I want to do is constrain that the type of r3 is some kind of
>  > range. I don't care what kind of range, it could be a range of integers,
>  > a range of floats, an input range, a forward range, and so on. I don't
>  > care which, but it has to be a range.
>
> It does exist in Phobos as inputRangeObject() (and ouputRangeObject).
> Although the name sounds limiting, inputRangeObject() can present any
> non-output range as a dynamically-typed range object.
>
> This example demonstrates how the programmer wanted an array of
> ForwardRange!int objects and inputRangeObject supported the need:
>
> import std.algorithm;
> import std.range;
> import std.stdio;
>
> void main() {
> int[] a1 = [1, 2, 3];
>
> ForwardRange!int r1 = inputRangeObject(map!"2 * a"(a1));
> ForwardRange!int r2 = inputRangeObject(map!"a ^^ 2"(a1));
>
> auto a2 = [r1, r2];
>
> writeln(a2);
> }
>
> That works because inputRangeObject uses 'static if' internally to
> determine what functionality the input range has.
>
> Note that r1 and r2 are based on two different original range types as
> the string delegate that the map() template takes makes the return type
> unique.
>
> The example can be changed like this to add any other ForwardRange!int
> to the existing ranges collection:
>
> auto a2 = [r1, r2];
> a2 ~= inputRangeObject([10, 20]);
> writeln(a2);
>
> Ali
>

IIRC, these are classes that come with all the typical runtime overhead, right?

I intend to try and keep the awesome mix of potentially optimal code that's also completely generalized.  Introducing hard-to-inline vtable calls into the mix would run against that goal.

If not for that, it would be close to what I'm looking for, minus the extraneous function call tacked onto every expression (which also makes it less discoverable).
July 29, 2012
On Sun, 29 Jul 2012 19:11:17 +0200, Chad J <chadjoan@__spam.is.bad__gmail.com> wrote:

>> So there. Now, you simply use auto a = constrain!isInputRange(
>> expression );. Is this what you wanted?
>
> That's pretty good.  It's still not as concise or easy to discover as the language's natural syntax for type declarations, but it's the kind of thing I'd use for my own purposes as a trick to get around language limitations.

It's likely as good as it gets without changing the language. Of course,
if you use this a lot with the same predicate, you could alias it to
something shorter:

alias constrain!isInputRange InputRange;

auto a = InputRange(expression);

Note also that the implementation supports multiple predicates, hence the
supplied hasElementType. And this is where aliases really come in handy:

template hasElementType( T ) {
    template hasElementType( U ) {
        enum hasElementType = is( ElementType!U == T );
    }
}

alias constrain!(isInputRange, hasElementType!int) IntRange;

auto a = IntRange([1,2,3]);

-- 
Simen
July 29, 2012
On 07/29/2012 10:22 AM, Chad J wrote:
> On 07/29/2012 11:54 AM, Ali Çehreli wrote:

>> ForwardRange!int r1 = inputRangeObject(map!"2 * a"(a1));
>> ForwardRange!int r2 = inputRangeObject(map!"a ^^ 2"(a1));

> IIRC, these are classes that come with all the typical runtime overhead,
> right?

Yes, inputRangeObject() allows runtime polymorphism over compile-time polymorphism.

> I intend to try and keep the awesome mix of potentially optimal code
> that's also completely generalized. Introducing hard-to-inline vtable
> calls into the mix would run against that goal.

Yes, usual runtime vs. compile-time polymorphism considerations apply.

Ali

1 2
Next ›   Last »