View mode: basic / threaded / horizontal-split · Log in · Help
July 23, 2012
Re: of "Conditional Implementation" vs "Assertive Input Validation"
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
July 23, 2012
Re: of "Conditional Implementation" vs "Assertive Input Validation"
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.
July 24, 2012
Re: of "Conditional Implementation" vs "Assertive Input Validation"
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
July 24, 2012
Re: of "Conditional Implementation" vs "Assertive Input Validation"
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.
July 24, 2012
Re: of "Conditional Implementation" vs "Assertive Input Validation"
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
July 25, 2012
Re: of "Conditional Implementation" vs "Assertive Input Validation"
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.
July 25, 2012
Re: of "Conditional Implementation" vs "Assertive Input Validation"
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
July 25, 2012
Re: of "Conditional Implementation" vs "Assertive Input Validation"
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.
Next ›   Last »
1 2
Top | Discussion index | About this forum | D home