I've been putting my nose in std.algorithm recently,
specifically, requirements of algorithms.
I must say I'm a big fan of conditional implementations. Given
the genericity of templates (!), it can be very hard to overload
them correct without conditional implementation. It is a really
useful language feature
That said, more often than not, they are also used to validate
input. While this is a good thing, when a developer DOES try to
call an algorithm with invalid input, he is greeted with the
cryptic:
"Error: template foo does not match any function template
declaration"
Followed by the (potentially long) list of candidates.
More often than not, the conditional implementations are actually
quite complicated, and contain up to 5 or 6 different conditions.
The worse part is that the developer has no idea _which_
condition he has violated.
This is a shame, since most of the time, the conditions are not
necessary for disambiguation, and a static assert would work just
as well, and be more verbose.
Simple example:
minPos doesn't shouldn't operate on infinite ranges. compare:
Range minPos(Range)(Range R1) if (is(inputRange!Range) &&
!isInfinite!Range) {...}
vs
Range minPos(Range)(Range R1) if (is(inputRange!Range)
{
static assert(!isInfinite!Range, "minPos cannot operate on
infinite ranges.")
...
}
Now, if a developer ever accidentally calls minPos with an
infinite range, he gets slapped in the face with a very explicit
compilation warning. This (I think) is great, because the
"isInfinite" test is really just an implementation detail.
inputRange is really the input condition of the range, and should
stay conditional.
--------
How do you feel about "assertive input validation" vs
"conditional implementation"?
Is this something you'd like to see more of in algorithm?

I've been putting my nose in std.algorithm recently, specifically,
requirements of algorithms.
I must say I'm a big fan of conditional implementations. Given the
genericity of templates (!), it can be very hard to overload them
correct without conditional implementation. It is a really useful
language feature
That said, more often than not, they are also used to validate input.
While this is a good thing, when a developer DOES try to call an
algorithm with invalid input, he is greeted with the cryptic:
"Error: template foo does not match any function template declaration"
Followed by the (potentially long) list of candidates.
More often than not, the conditional implementations are actually quite
complicated, and contain up to 5 or 6 different conditions.

I think this is really a problem with std.algorithm.
--
/Jacob Carlborg

It certainly makes sense. Maybe the long term solution would be
a way to embed those assertions in the conditions, but in the
meantime the proposal would alleviate the error message issue. I
know I've often enough stared long and hard at a volume of
template instantiation errors waiting for the real mistake to
jump out at me.
----------
And for the curious, what I mean by embedding assertions in the
conditions is quite literally what it sounds like:
Range minPos ( Range ) ( Range R1 )
if( is( inputRange!Range ) )
assert( !isInfinite!Range, "minPos cannot operate on infinite
ranges." )
{
...
}
In which case the compiler would merge the condition expressions
of the assertions into the if-condition, and remember the message
strings associated with them.

--------
How do you feel about "assertive input validation" vs "conditional
implementation"?
Is this something you'd like to see more of in algorithm?

The assertive input validation has the problem it prevents an overload
outside of std.algorithm from working.
I think we should focus here on the compiler giving better information
about which part of the Boolean constraint failed.
Andrei

The assertive input validation has the problem it prevents an overload
outside of std.algorithm from working.
I think we should focus here on the compiler giving better information
about which part of the Boolean constraint failed.

I would like to have something like this:
constraint InputRange (Range)
{
alias ElementType!(Range.init) E;
property bool empty ();
void popFront ();
property E front ();
}
constraint ForwardRange (Range) : ForwardRange!(Range)
{
E save ();
}
constraint BidirectionalRange (Range) : ForwardRange!(Range)
{
void popBack ();
property E back ();
property E front ();
}
It would be even better if the Range parameter and the ElementType could
be handled automatically:
constraint BidirectionalRange : ForwardRange
{
void popBack ();
property ElementType back ();
property ElementType front ();
}
And then to use the constraints, like this:
auto filterBidirectional (alias pred, Range :
BidirectionalRange!(Range)) (Range range);
Or even better if this would be possible:
auto filterBidirectional (alias pred) (BidirectionalRange range);
I know that others have had similar ideas. In these cases I think the
compiler should be able to give clear and proper error messages when
something fails to match because now the range constraints/interfaces
have names. Something like "Foo doesn't match BidirectionalRange" or
similar.
--
/Jacob Carlborg

[snip]
I know that others have had similar ideas. In these cases I
think the compiler should be able to give clear and proper
error messages when something fails to match because now the
range constraints/interfaces have names. Something like "Foo
doesn't match BidirectionalRange" or similar.

My fear is that no matter how you look at it, the compiler just
doesn't know which overload you want to call. If the compiler
could generate messages about why each overload was not
satisfactory, and it would have then to generate messages about
each and every possible overload, including the ones that you
obviously didn't want. Things would be better, but probably
over-verbose :/
My thought was not to remove conditional implementations
entirely, but rather to compliment them:
I think, for example, this would be perfect for equal:
"Takes two input ranges, and compares them with pred":
Notice that the english input condition here is merely "two input
ranges". If the range elements don't actually compare, it does
not mean you did not fulfill the "input contract", but rather
that you did not fulfill the "implementation requirements".
equal(alias pred = "a == b", R1, R2)(R1 range1, R2 range2)
if (isInputRange!R1 && isInputRange!R2)
{
assert(is(typeof(BinaryPred!pred(r1.front, r2.front))), "equal:
elements are not predicate comparable");
...
}
On Monday, 23 July 2012 at 14:47:50 UTC, Andrei Alexandrescu
wrote:

The assertive input validation has the problem it prevents an
overload outside of std.algorithm from working.
I think we should focus here on the compiler giving better
information about which part of the Boolean constraint failed.
Andrei

That is true, but then again, a certain level of ambiguity is "a
good thing"®. The compiler choosing too eagerly which function
you want to call based on obscure conditional implementations is
dangerous (IMO).
For example, if somebody else provided an "equal" which operated
on two input ranges, and took a unary predicated, and then used
the result of the unary predicate to compare the elements, I sure
as hell wouldn't it to be disambiguated without my explicit
consent.
If somebody writes a "fill" that can operate on input
(non-forward, non-infinite) ranges, I WANT the ambiguity.
----
I think the above gives a good balance of both approaches: The
conditional implementation is enough to *roughly* define what the
function operates on, while giving enough leeway for
non-ambiguous overloads. Once the compiler has *chosen* the
implementation though, then more thorough input validation can
kick in.
That's my take on it anyways.

My fear is that no matter how you look at it, the compiler just doesn't
know which overload you want to call. If the compiler could generate
messages about why each overload was not satisfactory, and it would have
then to generate messages about each and every possible overload,
including the ones that you obviously didn't want. Things would be
better, but probably over-verbose :/
My thought was not to remove conditional implementations entirely, but
rather to compliment them:
I think, for example, this would be perfect for equal:
"Takes two input ranges, and compares them with pred":
Notice that the english input condition here is merely "two input
ranges". If the range elements don't actually compare, it does not mean
you did not fulfill the "input contract", but rather that you did not
fulfill the "implementation requirements".
equal(alias pred = "a == b", R1, R2)(R1 range1, R2 range2)
if (isInputRange!R1 && isInputRange!R2)
{
assert(is(typeof(BinaryPred!pred(r1.front, r2.front))), "equal:
elements are not predicate comparable");
...
}

That looks even more horrible then the current std.algorithm. We
shouldn't have to add _more_ of these checks, rather remove. The
compiler should help the developer, not the other way around.
I think the signatures in std.algorithm are already too complicated and
verbose and they still don't protect from the horrible template errors
that template constraints were supposed to help avoid.
--
/Jacob Carlborg

I remember this was discussed recently and Don Clugston suggested
something like this:
if there is no match for:
void foo(T)(T t) if( !conditionA!T ){}
void foo(T)(T t) if( conditionA!T && conditionB!T ){}
the error will be:
template foo does not match any function template, constraints
values are:
conditionA: true
conditionB: false
(note that `conditionA` is listed only once)
And given who Don is, I'm pretty sure he have an idea how to
implement this.

I remember this was discussed recently and Don Clugston suggested
something like this:
if there is no match for:
void foo(T)(T t) if( !conditionA!T ){}
void foo(T)(T t) if( conditionA!T && conditionB!T ){}
the error will be:
template foo does not match any function template, constraints values are:
conditionA: true
conditionB: false
(note that `conditionA` is listed only once)
And given who Don is, I'm pretty sure he have an idea how to implement
this.

That will be just a little better. The problem is it will leak
implementation details. For this to be useful you really need to create
a template for every condition:
template isFoo (T)
{
enum isFoo = is(T == Foo);
}
void foo (T) (T t) if (isFoo!(T));
If you just do it like this:
void foo (T) (T t) if (is(T == Foo));
Then it will leak the "raw" condition. With my approach the constraint
will have a name which should result in a lot better error messages.
Think of it like this. You have a function, "foo", taking an interface
parameter, "Foo":
interface Foo
{
void a ();
void b ();
void c ();
}
void foo (Foo f);
You then call the function with an object that does _not_ implement the
function, then you get a proper easily understandable error message:
foo(new Object);
Error: Object doesn't implement Foo
What would you think if the error message look like this instead:
Error: Object doesn't have the methods:
void a ();
void b ();
void c ();
Now imagine that with a bunch of conditions instead.
void foo (T) (T t) if (__traits(compiles, { T t; t.foo; t.bar; }) ||
is(T : Foo));
Error: template foo does not match any function template, constraints
values are:
__traits(compiles, { T t; t.foo; t.bar }) false
is(T : Foo) false
--
/Jacob Carlborg

That will be just a little better. The problem is it will leak
implementation details. For this to be useful you really need
to create a template for every condition:
template isFoo (T)
{
enum isFoo = is(T == Foo);
}
void foo (T) (T t) if (isFoo!(T));
If you just do it like this:
void foo (T) (T t) if (is(T == Foo));
Then it will leak the "raw" condition.

hmm, yeah that could be hard for novices

With my approach the constraint will have a name which should
result in a lot better error messages.

Well it depends on that syntax is used. It's clearer if this syntax
would be possible:
void foo (InputRange range);
I think it's very little chance of a new language construct being added.
It's more like I don't necessarily think the way the template
constraints were implemented was the best way.

Actually the only thing constraint writer needs to know is that
`is(typeof(<code>))`
means "compiles". All the rest is perfectly clear.

And why is "(inout int _dummy=0)" there? I would guess that
std.algorithm and other parts of Phobos is filled with strange things
like this.

I think I've addressed these points, let me know if
I can explain it better.

Then the conclusion would at least be that std.algorithm could use some
enhancement how these template constraints are written.
--
/Jacob Carlborg

For this to be useful you really need to create a template
for every condition

[snip]

With my approach the constraint will have a name which should
result in a lot better error messages.

How is it different from creating a template for every
condition ? ;)

As long as you actually _do_ that, it might not be so much
different.

But you would have to create a constraint for every condition as
well!
And if someone implements Don's idea, error messages would be as
good.

Honestly, I don't quite get what's the difference between
current
template constraints and ones you're proposing:

[snip]

on the call site it's as convenient and clear as yours.
On the definition site, yes, syntax maybe is a tad worse,
but it's not *that* bad to require introduction of a new
language construct.

Well it depends on that syntax is used. It's clearer if this
syntax would be possible:
void foo (InputRange range);

1. How would you signify `foo` is generic ?
For complier it's probably possible - by type of `InputRange`,
but that will be one more special case
What about user ?
2. How would you put 2 constraints on `range` ?
3. Also you forgot to address:

Then the conclusion would at least be that std.algorithm could
use some enhancement how these template constraints are written.

Agree, `(inout int _dummy=0)` is confusing. There well may be
other (as confusing) examples but probably there are easy ways to
fix them.
Maybe you should create enhancement request or discuss it with
community ?
Wait! We were talking about how to make compiler error messages
better.