Reducing template constraint verbosity? [was Re: Slides from my ACCU Silicon Valley talk]

spir denis.spir at gmail.com
Tue Dec 14 09:35:10 PST 2010


On Tue, 14 Dec 2010 11:08:04 -0500
"Steven Schveighoffer" <schveiguy at yahoo.com> wrote:

> Having written a few of these functions with template constraints, I  
> wondered if there was ever any discussion/agreement on reducing verbosity  
> when specializing template constraints?
> 
> For instance, if you want two overloads of a template, one which accepts  
> types A and one which accepts types B where B implicitly converts to A  
> (i.e. a specialization), you need to explicitly reject B's when defining  
> the overload for A's.  For example:
> 
> 
> void foo(R)(R r) if(isRandomAccessRange!R) {...}
> 
> void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...}
> 
> 
> It seems redundant to specify !isRandomAccessRange!R in the second  
> overload, but the compiler will complain otherwise.  What sucks about this  
> is the 'definition' of the first overload is partially in the second.   
> That is, you don't really need that clause in the second overload unless  
> you define the first one.  Not only that, but it makes the template  
> constraints grow in complexity quite quickly.  Just look at a sample  
> function in std.array that handles 'the default' case:
> 
> 
> void popFront(A)(ref A a) if(!isNarrowString!A && isDynamicArray!A &&  
> isMutable!A && !is(A == void[]))
> 
> Any idea how this can be 'solved' or do we need to continue doing things  
> like this?  My naive instinct is to use the declaration order to determine  
> a match (first one to match wins), but that kind of goes against other  
> overloads in D.

Another issue: it is rather bug prone: if you introduce another specialised case, then it must be added to the series of negative constraints for the default case. And, in case this new specialisation is (partially) orthogonal, it must also be added as negative constraint to same or all other specialisations...

I seems to me D already all syntactic & semantic elements to cleanly express template constraints. I do not not understand why we do not reuse interfaces. Interfaces, I guess, allow clearly defining what is now implemented by ad hoc template check functions like isRandomAccessRange(T), and/or with esoteric uses of is().

Let us say we also reuse ':' as constraint-check operator.
	void f(T) () if (T:X)
would mean:
* if X is a type
    ~ T 'is' X
    ~ or T inherits X
* if X is an interface
    ~ T explicitely implements X (its definition starts with "struct/type T : X")
    ~ T factually implements X

   interface Writable { string toString (); }
   ...
   string listText (T) (T[] elements, string sep, string lDelim, string rDelim)
	if (T : Writable)
   { // returns eg "(e1 e2 e3...)" }

The notion of "factual implementation" is analog to duck typing, except it is a static, compile-time, fact. This does not solve the above-mentioned issue, but allows expressing it more clearly:

	void foo(R)(R r) if (R : InputRange && R !: RandomAccessRange) {...}

To fully solve the issue, we should have a way to express "non-implementation" in interfaces, for instance reuse constraints(!):

    interface StrictInputRange (T) : InputRange
	if (T !: RandomAccessRange && T !: ForwardRange ...) {}

Anyway, the result is:

    void foo(R)(R r) if (R : StrictInputRange) {...}



Denis
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com



More information about the Digitalmars-d mailing list