July 23, 2012
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
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
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
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
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
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
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
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.


1 2
Next ›   Last »