Re: of Conditional Implementation vs Assertive Input Validation

2012-07-25 Thread Jacob Carlborg

On 2012-07-25 07:30, xenon325 wrote:


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.


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

// yours
constraint InputRange (Range)
{
 alias ElementType!(Range.init) E;

 @property bool empty ();
 void popFront ();
 @property E front ();
}

// std.range
//
https://github.com/D-Programming-Language/phobos/blob/master/std/range.d#L549

template isInputRange(R)
{
 enum bool isInputRange = is(typeof(
 (inout int _dummy=0)
 {
 R r = void;   /*** how would you express this b.t.w. ? ***/
 if (r.empty) {}
 r.popFront();
 auto h = r.front;
 }));
}

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);

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


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-25 Thread xenon325

On Wednesday, 25 July 2012 at 07:23:51 UTC, Jacob Carlborg wrote:

On 2012-07-25 07:30, xenon325 wrote:

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:


template isInputRange(R)
{
 enum bool isInputRange = is(typeof(
 (inout int _dummy=0)
 {
 R r = void;   /*** how would you express this 
b.t.w. ? ***/


   range.d comment on this line is can define a range object.


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.


Sorry, but I still fail to see how you way is (significantly) 
better.




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.


Yeah :) My thoughts (have really little D experience):
1. why is it lambda
2. why does it have argument
3. why is arg `inout`
4. why is *dummy* arg initialized

I would think it's some kind of temporary compiler workaround.
But if it's something smart, it could definitely use
better syntax, no objections here (__traits( compiles, ..) ?).


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.





Re: of Conditional Implementation vs Assertive Input Validation

2012-07-24 Thread Jacob Carlborg

On 2012-07-23 22:28, monarch_dodra wrote:


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


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-24 Thread xenon325

On Monday, 23 July 2012 at 19:57:42 UTC, Jacob Carlborg wrote:

constraint BidirectionalRange (Range) : ForwardRange!(Range)
{
void popBack ();
@property E back ();
@property E front ();
}

 [snip]

I know that others have had similar ideas.


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.





Re: of Conditional Implementation vs Assertive Input Validation

2012-07-24 Thread Jacob Carlborg

On 2012-07-24 10:37, xenon325 wrote:


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


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-24 Thread xenon325

On Tuesday, 24 July 2012 at 12:50:08 UTC, Jacob Carlborg wrote:

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.


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


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


// yours
constraint InputRange (Range)
{
alias ElementType!(Range.init) E;

@property bool empty ();
void popFront ();
@property E front ();
}

// std.range
// 
https://github.com/D-Programming-Language/phobos/blob/master/std/range.d#L549

template isInputRange(R)
{
enum bool isInputRange = is(typeof(
(inout int _dummy=0)
{
R r = void;   /*** how would you express this b.t.w. 
? ***/

if (r.empty) {}
r.popFront();
auto h = r.front;
}));
}

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.

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


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


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



of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread monarch_dodra
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?


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread Tobias Pankrath

On Monday, 23 July 2012 at 11:10:39 UTC, monarch_dodra wrote:


Is this something you'd like to see more of in algorithm?


Yes! Improving error messages is very important. Maybe we find a 
better solution on compiler level but in the meantime [not only] 
std.algorithm will benefit from your solution.


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread Chris NS
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.


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread Jacob Carlborg

On 2012-07-23 13:10, monarch_dodra wrote:

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


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread Philippe Sigaud
On Mon, Jul 23, 2012 at 1:44 PM, Chris NS ibisbase...@gmail.com wrote:

 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. )

Would it be possible to create a If / Assert template that returns
(er, becomes) true if the condition is true, and static asserts if not
?

template Assert(bool condition, string message)
{
static if (condition)
enum Assert = true;
else
static assert(false, message);
}

void foo(T,U)(T t, U u) if (is(T == int)  Assert!(isIntegral!U, U
must be an integer type))
{}


void main()
{
foo(1,a);
}

Seems to work (tm).


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread Dmitry Olshansky

On 23-Jul-12 17:29, Philippe Sigaud wrote:

On Mon, Jul 23, 2012 at 1:44 PM, Chris NS ibisbase...@gmail.com wrote:


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. )


Would it be possible to create a If / Assert template that returns
(er, becomes) true if the condition is true, and static asserts if not
?



Will explode every time a constraint is tested?
After all constraints do fail so that other function is picked up and 
they are supposed to.



template Assert(bool condition, string message)
{
 static if (condition)
 enum Assert = true;
 else
 static assert(false, message);
}

void foo(T,U)(T t, U u) if (is(T == int)  Assert!(isIntegral!U, U
must be an integer type))
{}


void main()
{
 foo(1,a);
}

Seems to work (tm).




--
Dmitry Olshansky


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread Philippe Sigaud
On Mon, Jul 23, 2012 at 3:39 PM, Dmitry Olshansky dmitry.o...@gmail.com wrote:

 Will explode every time a constraint is tested?
 After all constraints do fail so that other function is picked up and they
 are supposed to.

Damn, you're right.


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread Simen Kjaeraas
On Mon, 23 Jul 2012 15:49:43 +0200, Philippe Sigaud  
philippe.sig...@gmail.com wrote:


On Mon, Jul 23, 2012 at 3:39 PM, Dmitry Olshansky  
dmitry.o...@gmail.com wrote:



Will explode every time a constraint is tested?
After all constraints do fail so that other function is picked up and  
they

are supposed to.


Damn, you're right.


To be fair, it explodes as often as the if (isInputRange!T){static  
assert(!isInfinite!T);} does.


--
Simen


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread Dmitry Olshansky

On 23-Jul-12 18:07, Simen Kjaeraas wrote:

On Mon, 23 Jul 2012 15:49:43 +0200, Philippe Sigaud
philippe.sig...@gmail.com wrote:


On Mon, Jul 23, 2012 at 3:39 PM, Dmitry Olshansky
dmitry.o...@gmail.com wrote:


Will explode every time a constraint is tested?
After all constraints do fail so that other function is picked up and
they
are supposed to.


Damn, you're right.


To be fair, it explodes as often as the if (isInputRange!T){static
assert(!isInfinite!T);} does.



Right. It's just I did something similar to Philippe stuff previously 
and this somehow was an unpleasant surprise for me.


--
Dmitry Olshansky


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread Andrei Alexandrescu

On 7/23/12 7:10 AM, monarch_dodra wrote:
[snip]


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


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread Jacob Carlborg

On 2012-07-23 16:47, 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.


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


Re: of Conditional Implementation vs Assertive Input Validation

2012-07-23 Thread monarch_dodra

On Monday, 23 July 2012 at 19:57:42 UTC, Jacob Carlborg wrote:

[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.