www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Expressing range constraints in CNF form

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
https://github.com/dlang/phobos/pull/5461

There's many good advantages to this. The immediate one is the 
constraint is better structured and easier to understand. Then, the 
compiler can print the exact clause that failed, which improves the 
precision and quality of the error message.

We consider adding in the future some capability of printing 
user-defined error messages, one per clause. For example:

// Current
enum bool isInputRange(R) =
     is(typeof((ref R r) => r))
     && is(ReturnType!((R r) => r.empty) == bool)
     && is(typeof(lvalueOf!R.front))
     && is(typeof(lvalueOf!R.popFront));

// Possible (which change to language)
enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && "must be copyable"
     && is(ReturnType!((R r) => r.empty) == bool) && "must support bool 
empty"
     && is(typeof(lvalueOf!R.front)) && "must support front"
     && is(typeof(lvalueOf!R.popFront)) && "must support back";

// Also possible (no change to the language)
enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && msg("must be copyable")
     && is(ReturnType!((R r) => r.empty) == bool) && msg("must support 
bool empty")
     && is(typeof(lvalueOf!R.front)) && msg("must support front")
     && is(typeof(lvalueOf!R.popFront)) && msg("must support back");


Andrei
Jun 10
next sibling parent reply Nick Treleaven <nick geany.org> writes:
On Sunday, 11 June 2017 at 00:28:58 UTC, Andrei Alexandrescu 
wrote:
 https://github.com/dlang/phobos/pull/5461

 There's many good advantages to this. The immediate one is the 
 constraint is better structured and easier to understand. Then, 
 the compiler can print the exact clause that failed, which 
 improves the precision and quality of the error message.
Great!
 // Also possible (no change to the language)
 enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && msg("must be copyable")
     && is(ReturnType!((R r) => r.empty) == bool) && msg("must 
 support bool empty")
     && is(typeof(lvalueOf!R.front)) && msg("must support front")
     && is(typeof(lvalueOf!R.popFront)) && msg("must support 
 back");
I'm not getting how this works.
Jun 11
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/11/17 11:11 AM, Nick Treleaven wrote:
 On Sunday, 11 June 2017 at 00:28:58 UTC, Andrei Alexandrescu wrote:
 https://github.com/dlang/phobos/pull/5461

 There's many good advantages to this. The immediate one is the 
 constraint is better structured and easier to understand. Then, the 
 compiler can print the exact clause that failed, which improves the 
 precision and quality of the error message.
Great!
Thanks.
 // Also possible (no change to the language)
 enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && msg("must be copyable")
     && is(ReturnType!((R r) => r.empty) == bool) && msg("must support 
 bool empty")
     && is(typeof(lvalueOf!R.front)) && msg("must support front")
     && is(typeof(lvalueOf!R.popFront)) && msg("must support back");
I'm not getting how this works.
Ostensibly the function is trivial: bool msg(string) { return true; } It doesn't change the semantics. The compiler would recognize it as an intrinsic and would print the message if the clause to its left has failed. Andrei
Jun 11
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 11 June 2017 at 15:25:11 UTC, Andrei Alexandrescu 
wrote:
 On 6/11/17 11:11 AM, Nick Treleaven wrote:
 On Sunday, 11 June 2017 at 00:28:58 UTC, Andrei Alexandrescu 
 wrote:
 https://github.com/dlang/phobos/pull/5461

 There's many good advantages to this. The immediate one is 
 the constraint is better structured and easier to understand. 
 Then, the compiler can print the exact clause that failed, 
 which improves the precision and quality of the error message.
Great!
Thanks.
 // Also possible (no change to the language)
 enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && msg("must be copyable")
     && is(ReturnType!((R r) => r.empty) == bool) && msg("must 
 support bool empty")
     && is(typeof(lvalueOf!R.front)) && msg("must support 
 front")
     && is(typeof(lvalueOf!R.popFront)) && msg("must support 
 back");
I'm not getting how this works.
Ostensibly the function is trivial: bool msg(string) { return true; } It doesn't change the semantics. The compiler would recognize it as an intrinsic and would print the message if the clause to its left has failed. Andrei
Exposing magic functions which are recognized by name strikes me as being worse then proper compiler intrinsics.
Jun 11
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/11/17 12:15 PM, Stefan Koch wrote:
 Exposing magic functions which are recognized by name strikes me as 
 being worse then proper compiler intrinsics.
This is brainstorming. Let's follow up with better ideas in addition to pointing out flaws in the ideas on the table. To this point: .msg would solve the problem of "msg" being defined in the current scope (assuming of course msg is defined in object.d). Andrei
Jun 11
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11.06.2017 17:25, Andrei Alexandrescu wrote:
 On 6/11/17 11:11 AM, Nick Treleaven wrote:
 On Sunday, 11 June 2017 at 00:28:58 UTC, Andrei Alexandrescu wrote:
 https://github.com/dlang/phobos/pull/5461

 There's many good advantages to this. The immediate one is the 
 constraint is better structured and easier to understand. Then, the 
 compiler can print the exact clause that failed, which improves the 
 precision and quality of the error message.
Great!
Thanks.
 // Also possible (no change to the language)
 enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && msg("must be copyable")
     && is(ReturnType!((R r) => r.empty) == bool) && msg("must support 
 bool empty")
     && is(typeof(lvalueOf!R.front)) && msg("must support front")
     && is(typeof(lvalueOf!R.popFront)) && msg("must support back");
I'm not getting how this works.
Ostensibly the function is trivial: bool msg(string) { return true; } It doesn't change the semantics. The compiler would recognize it as an intrinsic and would print the message if the clause to its left has failed. Andrei
I'd prefer bool msg(bool constraint, string message){ return constraint; } This does not require the compiler to dive into a branch it wouldn't consider otherwise, and the pairing of constraint to message is less ad-hoc.
Jun 11
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Sunday, 11 June 2017 at 16:28:23 UTC, Timon Gehr wrote:

 I'd prefer

 bool msg(bool constraint, string message){ return constraint; }

 This does not require the compiler to dive into a branch it 
 wouldn't consider otherwise, and the pairing of constraint to 
 message is less ad-hoc.
Where were you while Steven was destroying me? :) http://forum.dlang.org/thread/mcxeymbslqtvfijxirmy forum.dlang.org
Jun 11
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 6/11/17 12:35 PM, Stanislav Blinov wrote:
 On Sunday, 11 June 2017 at 16:28:23 UTC, Timon Gehr wrote:

 I'd prefer

 bool msg(bool constraint, string message){ return constraint; }

 This does not require the compiler to dive into a branch it wouldn't
 consider otherwise, and the pairing of constraint to message is less
 ad-hoc.
Where were you while Steven was destroying me? :) http://forum.dlang.org/thread/mcxeymbslqtvfijxirmy forum.dlang.org
Heh, I both like the idea of switching the constraints to something that identifies easily a single test of the constraint instead of a lambda that can fail for any number of reasons, and also think we still do not need a message. i.e.: is(typeof(lvalueOf!R.front)) && "must support front" "must support front" seems pretty obvious from the test. -Steve
Jun 11
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/11/17 12:28 PM, Timon Gehr wrote:
 bool msg(bool constraint, string message){ return constraint; }
That'd be nice to consider, too, thanks. -- Andrei
Jun 11
prev sibling next sibling parent Antonio Corbi <acorbi ggmail.xml> writes:
On Sunday, 11 June 2017 at 15:25:11 UTC, Andrei Alexandrescu 
wrote:
 On 6/11/17 11:11 AM, Nick Treleaven wrote:
 On Sunday, 11 June 2017 at 00:28:58 UTC, Andrei Alexandrescu 
 wrote:
 [...]
Great!
Thanks.
 [...]
I'm not getting how this works.
Ostensibly the function is trivial: bool msg(string) { return true; } It doesn't change the semantics. The compiler would recognize it as an intrinsic and would print the message if the clause to its left has failed. Andrei
So this is trying to parafrase the perl/bash idiom: ``` commandSucceeds (...) || die (); ``` Isn't it? So, the '&&' -and- shouldn't be '||' -or-? Sorry for the noise if I'm wrong. Antonio
Jun 11
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 6/11/2017 8:25 AM, Andrei Alexandrescu wrote:
 Ostensibly the function is trivial:
 
 bool msg(string) { return true; }
 
 It doesn't change the semantics. The compiler would recognize it as an
intrinsic 
 and would print the message if the clause to its left has failed.
There was a proposal a while back to enable CTFE to print messages, which is probably a better solution. msg() could be something along the lines of: bool msg(string) { __ctfeprint(string); return true; } which would involve considerably less compiler magic. Furthermore, `msg` could be a local private function, which would avoid "but I'm already using `msg`" problems.
Jun 11
next sibling parent ketmar <ketmar ketmar.no-ip.org> writes:
Walter Bright wrote:

 On 6/11/2017 8:25 AM, Andrei Alexandrescu wrote:
 Ostensibly the function is trivial:
 bool msg(string) { return true; }
 It doesn't change the semantics. The compiler would recognize it as an 
 intrinsic and would print the message if the clause to its left has 
 failed.
There was a proposal a while back to enable CTFE to print messages, which is probably a better solution. msg() could be something along the lines of: bool msg(string) { __ctfeprint(string); return true; } which would involve considerably less compiler magic. Furthermore, `msg` could be a local private function, which would avoid "but I'm already using `msg`" problems.
that has another problem: there may be overload set, where some overloads are failed, but one is fitting. using `ctfeprint` will spam user with alot of non-sensical messages about failed constraints.
Jun 11
prev sibling parent reply ketmar <ketmar ketmar.no-ip.org> writes:
Walter Bright wrote:

 On 6/11/2017 8:25 AM, Andrei Alexandrescu wrote:
 Ostensibly the function is trivial:
 bool msg(string) { return true; }
 It doesn't change the semantics. The compiler would recognize it as an 
 intrinsic and would print the message if the clause to its left has 
 failed.
There was a proposal a while back to enable CTFE to print messages, which is probably a better solution. msg() could be something along the lines of: bool msg(string) { __ctfeprint(string); return true; } which would involve considerably less compiler magic. Furthermore, `msg` could be a local private function, which would avoid "but I'm already using `msg`" problems.
i think, that something like `__constraint(condition, "message")` is ok, and it should be built-in, just like `__traits()`. so compiler can collect those messages, and only show 'em if the matcher is failed to find anything. yeah, another hack in interpreter, but fairly small, and should solve "what constraint is really failed" problem.
Jun 11
next sibling parent Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 11 June 2017 at 19:51:11 UTC, ketmar wrote:
 Walter Bright wrote:

 [...]
i think, that something like `__constraint(condition, "message")` is ok, and it should be built-in, just like `__traits()`. so compiler can collect those messages, and only show 'em if the matcher is failed to find anything. yeah, another hack in interpreter, but fairly small, and should solve "what constraint is really failed" problem.
+1 Implementation wise this one is easiest ... I guess.
Jun 11
prev sibling parent bachmeier <no spam.net> writes:
On Sunday, 11 June 2017 at 19:51:11 UTC, ketmar wrote:

 i think, that something like `__constraint(condition, 
 "message")` is ok, and it should be built-in, just like 
 `__traits()`. so compiler can collect those messages, and only 
 show 'em if the matcher is failed to find anything. yeah, 
 another hack in interpreter, but fairly small, and should solve 
 "what constraint is really failed" problem.
This is really nice. I like it better than my suggestion.
Jun 11
prev sibling parent bachmeier <no spam.net> writes:
On Sunday, 11 June 2017 at 15:25:11 UTC, Andrei Alexandrescu 
wrote:

 // Also possible (no change to the language)
 enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && msg("must be copyable")
     && is(ReturnType!((R r) => r.empty) == bool) && msg("must 
 support bool empty")
     && is(typeof(lvalueOf!R.front)) && msg("must support 
 front")
     && is(typeof(lvalueOf!R.popFront)) && msg("must support 
 back");
This would be a very nice change, but this syntax feels more natural to me: is(ReturnType!((R r) => r.empty) == bool, "must support bool empty")
Jun 11
prev sibling next sibling parent reply crimaniak <crimaniak gmail.com> writes:
On Sunday, 11 June 2017 at 00:28:58 UTC, Andrei Alexandrescu 
wrote:
 enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && "must be copyable"
Regardless of the implementation method, this will require the previously proposed Phobos refactoring. Independent special definitions have to be reduced to general ones and use "static if" to select the algorithm. Otherwise, each failed independent definition will give an additional error message, and the compiler does not have the ability to determine which one is relevant.
Jun 11
parent Walter Bright <newshound2 digitalmars.com> writes:
On 6/11/2017 12:07 PM, crimaniak wrote:
 Regardless of the implementation method, this will require the previously 
 proposed Phobos refactoring. Independent special definitions have to be
reduced 
 to general ones and use "static if" to select the algorithm. Otherwise, each 
 failed independent definition will give an additional error message, and the 
 compiler does not have the ability to determine which one is relevant.
You're right that there are issues with the `msg` thing in the presence of overloading.
Jun 11
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 6/10/2017 5:28 PM, Andrei Alexandrescu wrote:
      && is(typeof(lvalueOf!R.popFront)) && msg("must support back");
Wouldn't it work better as: && (is(typeof(lvalueOf!R.popFront)) || msg("must support back")); bool msg(string) { return false; } ? Then msg() is only called if the first clause fails.
Jun 11
next sibling parent Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 11 June 2017 at 19:45:37 UTC, Walter Bright wrote:
 On 6/10/2017 5:28 PM, Andrei Alexandrescu wrote:
      && is(typeof(lvalueOf!R.popFront)) && msg("must support 
 back");
Wouldn't it work better as: && (is(typeof(lvalueOf!R.popFront)) || msg("must support back")); bool msg(string) { return false; } ? Then msg() is only called if the first clause fails.
I can do a __ctfePrint for string literals. (is(typeof(lvalueOf!R.popFront)) || __ctfePrint("bla")) would work in a short amount of time.
Jun 11
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/11/17 3:45 PM, Walter Bright wrote:
 On 6/10/2017 5:28 PM, Andrei Alexandrescu wrote:
      && is(typeof(lvalueOf!R.popFront)) && msg("must support back");
Wouldn't it work better as: && (is(typeof(lvalueOf!R.popFront)) || msg("must support back")); bool msg(string) { return false; } ? Then msg() is only called if the first clause fails.
The added parens are a small liability. -- Andrei
Jun 11
prev sibling next sibling parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
On Sunday, 11 June 2017 at 00:28:58 UTC, Andrei Alexandrescu 
wrote:
 // Also possible (no change to the language)
 enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && msg("must be copyable")
     && is(ReturnType!((R r) => r.empty) == bool) && msg("must 
 support bool empty")
     && is(typeof(lvalueOf!R.front)) && msg("must support front")
     && is(typeof(lvalueOf!R.popFront)) && msg("must support 
 back");


 Andrei
What about using ddoc? enum bool isInputRange(R) = is(typeof((ref R r) => r)) /// must be copyable && is(ReturnType!((R r) => r.empty) == bool) /// must support bool empty && is(typeof(lvalueOf!R.front)) /// must support front && is(typeof(lvalueOf!R.popFront)) /// must support back
Jun 11
parent reply David Gileadi <gileadisNOSPM gmail.com> writes:
On 6/11/17 1:32 PM, Sebastiaan Koppe wrote:

 What about using ddoc?
 
 enum bool isInputRange(R) =
      is(typeof((ref R r) => r)) /// must be copyable
      && is(ReturnType!((R r) => r.empty) == bool) /// must support bool 
 empty
      && is(typeof(lvalueOf!R.front)) /// must support front
      && is(typeof(lvalueOf!R.popFront)) /// must support back
If there's going to be compiler magic, this seems like a sensible syntax--documenting constraints is a good idea anyway, particularly for those of us who aren't is-expression masters.
Jun 12
next sibling parent Sebastiaan Koppe <mail skoppe.eu> writes:
On Monday, 12 June 2017 at 17:35:59 UTC, David Gileadi wrote:
 On 6/11/17 1:32 PM, Sebastiaan Koppe wrote:

 What about using ddoc?
 
 enum bool isInputRange(R) =
      is(typeof((ref R r) => r)) /// must be copyable
      && is(ReturnType!((R r) => r.empty) == bool) /// must 
 support bool empty
      && is(typeof(lvalueOf!R.front)) /// must support front
      && is(typeof(lvalueOf!R.popFront)) /// must support back
If there's going to be compiler magic, this seems like a sensible syntax--documenting constraints is a good idea anyway, particularly for those of us who aren't is-expression masters.
Yeah, I had meant to mention the compiler magic involved. I suppose the magic is far less than anything else though, since the compiler already associates surrounding docs to code (right?).
Jun 12
prev sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Mon, Jun 12, 2017 at 10:35:59AM -0700, David Gileadi via Digitalmars-d wrote:
 On 6/11/17 1:32 PM, Sebastiaan Koppe wrote:
 
 What about using ddoc?
 
 enum bool isInputRange(R) =
      is(typeof((ref R r) => r)) /// must be copyable
      && is(ReturnType!((R r) => r.empty) == bool) /// must support bool
 empty
      && is(typeof(lvalueOf!R.front)) /// must support front
      && is(typeof(lvalueOf!R.popFront)) /// must support back
If there's going to be compiler magic, this seems like a sensible syntax--documenting constraints is a good idea anyway, particularly for those of us who aren't is-expression masters.
+1, I like this idea much better than any of the others proposed so far. No need for additional syntax, reuse ddoc comment syntax for something that, ostensibly, could be considered documentation. T -- If lightning were to ever strike an orchestra, it'd always hit the conductor first.
Jun 12
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Sunday, 11 June 2017 at 00:28:58 UTC, Andrei Alexandrescu 
wrote:
 // Also possible (no change to the language)
 enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && msg("must be copyable")
Or how about enum bool isInputRange(R) = isCopyable!R && supportsBoolEmpty!R && supportsFront!R && supportsPopFront!R; and the compiler just calls out which condition didn't match?
     && is(typeof(lvalueOf!R.popFront)) && msg("must support 
 back");
hey look you already messed up the string by repeating it.
Jun 11
next sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On Monday, 12 June 2017 at 00:13:46 UTC, Adam D. Ruppe wrote:
 On Sunday, 11 June 2017 at 00:28:58 UTC, Andrei Alexandrescu 
 wrote:
 // Also possible (no change to the language)
 enum bool isInputRange(R) =
     is(typeof((ref R r) => r)) && msg("must be copyable")
Or how about enum bool isInputRange(R) = isCopyable!R && supportsBoolEmpty!R && supportsFront!R && supportsPopFront!R; and the compiler just calls out which condition didn't match?
Much better. Probably could be a bit more generic than that :)
     && is(typeof(lvalueOf!R.popFront)) && msg("must support 
 back");
hey look you already messed up the string by repeating it.
Exactly my point. -Steve
Jun 11
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 6/11/2017 5:13 PM, Adam D. Ruppe wrote:
        supportsFront!R &&
That doesn't work because there may be a local symbol: T front(R r) { } which will not be in scope in supportsFront().
Jun 12
next sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On Monday, 12 June 2017 at 07:18:30 UTC, Walter Bright wrote:
 On 6/11/2017 5:13 PM, Adam D. Ruppe wrote:
        supportsFront!R &&
That doesn't work because there may be a local symbol: T front(R r) { } which will not be in scope in supportsFront().
That is a problem regardless of the constraint. Even if we are clever enough to construct the constraint to workaround this issue, code that uses such ranges will not compile. -Steve
Jun 12
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 06/12/2017 03:18 AM, Walter Bright wrote:
 On 6/11/2017 5:13 PM, Adam D. Ruppe wrote:
        supportsFront!R &&
That doesn't work because there may be a local symbol: T front(R r) { } which will not be in scope in supportsFront().
Yah, this was part of the original design: migrate all checks to std.traits. At a point I realized that queries of the form "does a.b work?" need to be accompanied by passing a number of modules to import in order to resolve possible UFCS for "b". At the moment I had a standard library artifact import a user-defined module, I decided things got too odd and gave up on that. -- Andrei
Jun 12