Differing levels of type-inference: Can D do this?

Chad J chadjoan at __spam.is.bad__gmail.com
Sat Jul 28 13:47:01 PDT 2012


On 07/28/2012 03:03 AM, Jonathan M Davis wrote:
> On Saturday, July 28, 2012 02:49:16 Chad J wrote:
>> Is there some way to do something similar to this right now?
>>
>> void main()
>> {
>>     // Differing levels of type-inference:
>>     int[]       r1 = [1,2,3]; // No type-inference.
>
> That works just fine.
>

Of course.  It's provided as a reference for one extreme.

>>     Range!(int) r2 = [1,2,3]; // Only range kind inferred.
>
> What do you mean by range kind? If you declare Range!int, you gave it its type
> already. It's whatever Range!int is. You can't change it. There's nothing to
> infer. Either Range!int has a constructor which takes an int[] or the
> initialization won't work. Range!int is the same either way.
>

"range kind" is informal language.  Maybe I mean "template instances", 
but that would somewhat miss the point.

I don't know how to do this right now.  AFAIK, it's not doable.
When I speak of ranges I refer specifically to the std.phobos ranges. 
There is no Range type right now, but there are the isInputRange, 
isOutputRange, isForwardRange, etc. templates that define what a Range 
is.  The problem is that I have no idea how to write something like this:

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.

There also seem to be some wrappers in std.range, but they seem to have 
caveats and runtime overhead (OOP interfaces imply vtable usage, etc).

>>     Range       r3 = [1,2,3]; // Element type inferred.
>
> Again, the type of the variable must already be a full type, or you can't
> declare it. So, there's nothing to infer. Either Range!int has a constructor
> which takes an int[] or the initialization won't work. Range is the same
> either way.
>

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.

>>     auto        r4 = [1,2,3]; // Full type-inference.
>
> This works;
>

Yep.  It's the other end of the extreme.  What I'm missing is the stuff 
in the middle.

>> AFAIK it isn't: the type system is pretty much all-or-nothing about
>> this.  Please show me that I'm wrong.
>
> The _only_ time that the type on the left-hand side of an assignment
> expression depends on the type of the right-hand side is if the type is being
> explicitly inferred by using auto, const, immutable, or enum as the type by
> themselves. Types are never magically altered by what you assign to them.
>
> If you want to, you can create a templated function which picks the type to
> return. e.g.
>
> Type var = makeType([1, 2, 3]);
>
> or
>
> auto var = makeType([1, 2, 3]);
>
> but it's the function which determines what the type is.
>
> - Jonathan M Davis

which seems like the "makeSureItsARange" solution I mentioned above. 
It's out of place because traditionally we could constrain the types of 
things on the parameters and returns of functions in exactly the same 
manner as we could constrain variable declarations (give or take some 
storage classes).  But with the new notion of structural conformity of 
types in D, I don't see how we can give variables the same type 
constraints that function parameters are allowed to use.

This seems like the kind of thing that compile-time struct 
inheritance/interfaces would solve:

struct interface InputRange(T)
{
	T @property front();
	T popFront();
	bool @property empty();
}

struct MyRange(T) : InputRange!T
{
	private T[] payload;
	T front() { return payload[0]; }
	T popFront() { payload = payload[1..$]; return payload; }
	bool @property empty() { return payload.length; }
	...
}

// We can tell from looking at the below line that r is
//   an InputRange!int.  If it was "auto" instead,
//   we'd have no idea without look at the docs, and
//   we wouldn't be able to localize future type-mismatches
//   to this location in the scope/file/whatever.
InputRange!int r = someFunctionThatReturnsAMyRange([1,2,3]);

But I wanted to check and see if there was some way of doing this already.


More information about the Digitalmars-d-learn mailing list