June 21, 2017 Re: DIP 1009--Improve Contract Usability--Preliminary Review Round 1 | ||||
---|---|---|---|---|
| ||||
Posted in reply to MysticZach | On Wednesday, 21 June 2017 at 09:27:20 UTC, MysticZach wrote:
> On Wednesday, 21 June 2017 at 08:15:34 UTC, MysticZach wrote:
>> On Wednesday, 21 June 2017 at 04:16:22 UTC, Moritz Maxeiner wrote:
>>> int myFunc(Args...)(Args args)
>>> if (Args.length > 2)
>>> in (args[0] != 0)
>>> in (args[1] > 1)
>>> out (result => result > 0) { ... }
avoiding the "anonymous scope"-extra wouldnt hurt much?
int myFunc(Args...)(Args args)
if (Args.length > 2)
in (args[0] != 0)
in (args[1] > 1)
out (result => result > 0)
do { ... }
|
June 21, 2017 Re: DIP 1009--Improve Contract Usability--Preliminary Review Round 1 | ||||
---|---|---|---|---|
| ||||
Posted in reply to MysticZach | On Wednesday, 21 June 2017 at 09:10:33 UTC, MysticZach wrote:
> On Wednesday, 21 June 2017 at 05:19:26 UTC, H. S. Teoh wrote:
>> On Wed, Jun 21, 2017 at 01:06:40AM +0000, MysticZach via Digitalmars-d wrote:
>>> On Tuesday, 20 June 2017 at 21:04:16 UTC, Steven Schveighoffer wrote:
>>> > This is much much better. The verbosity of contracts isn't really the brace, it's the asserts.
>>>
>>> I think it's both, and I think the brace is the only thing that can be improved upon. How could you justify insisting that everyone use the built-in asserts for their contracts?
>> [...]
>>
>> Umm... I think we're not quite on the same page here. What *else* are people supposed to use inside their contracts besides the built-in assert??
>
> Many people have expressed discontent with existing asserts. In fact, they were just changed yesterday to address one of these concerns:
>
> https://github.com/dlang/dmd/pull/6901
Just my two cents, again: DbC prohibits broken contracts, i.e. any violation is a bug. From my point of view, a broken contract in the simple syntax as proposed by H.S.Teoh, should by default
- in debug mode: Give you debug information (stack trace, etc.) then terminate
- in release mode: Print file and line number and then terminate
No cleanup by default.
People I've observed voicing issues with assert have happened to fall into one of these three categories:
- People who agree with the terminate approach, but want custom cleanup
I remain skeptical about the sanity of calling arbitrary code with the knowledge of a previously triggered bug (in release mode), but that can be easily addressed by allowing to register a hook that gets called on contract violations; the process will still terminate after the hook is finished, though.
- People whose use cases supposedly allows recovery of bugs
These are niche cases that can be covered by the preexisting verbose syntax: `in { if (!cond) throw Exception(); }`. I would simply not include support for that in the easy-to-read variant.
- People who use assert (or other Errors) for user input validation
That's not what they are for.
To sum it up, I would like semantics similar to this (simplified code) for this syntax:
---
void delegate() @nothrow onContractViolation;
void validateContract(bool)(lazy bool cond)
{
version (CheckContracts) if (!cond)
{
import core.stdc.stdlib : _Exit;
debug printDebugInfo(...);
else printFileAndLIne();
{
if (onContractViolation !is null) onContractViolation();
_Exit(1);
} else {
print
}
}
}
---
|
June 21, 2017 Re: DIP 1009--Improve Contract Usability--Preliminary Review Round 1 | ||||
---|---|---|---|---|
| ||||
Posted in reply to MysticZach | On Wednesday, 21 June 2017 at 09:10:33 UTC, MysticZach wrote:
> On Wednesday, 21 June 2017 at 05:19:26 UTC, H. S. Teoh wrote:
>> On Wed, Jun 21, 2017 at 01:06:40AM +0000, MysticZach via Digitalmars-d wrote:
>>> On Tuesday, 20 June 2017 at 21:04:16 UTC, Steven Schveighoffer wrote:
>>> > This is much much better. The verbosity of contracts isn't really the brace, it's the asserts.
>>>
>>> I think it's both, and I think the brace is the only thing that can be improved upon. How could you justify insisting that everyone use the built-in asserts for their contracts?
>> [...]
>>
>> Umm... I think we're not quite on the same page here. What *else* are people supposed to use inside their contracts besides the built-in assert??
>
> Many people have expressed discontent with existing asserts. In fact, they were just changed yesterday to address one of these concerns:
>
> https://github.com/dlang/dmd/pull/6901
Just my two cents, again: DbC prohibits broken contracts, i.e. any violation is a bug. From my point of view, a broken contract in the simple syntax as proposed by H.S.Teoh, should by default
- in debug mode: Give you debug information (stack trace, etc.) then terminate
- in release mode: Print file and line number and then terminate
No cleanup by default.
People I've observed voicing issues with assert have happened to fall into one of these three categories:
- People who agree with the terminate approach, but want custom cleanup
I remain skeptical about the sanity of calling arbitrary code with the knowledge of a previously triggered bug (in release mode), but that can be easily addressed by allowing to register a hook that gets called on contract violations; the process will still terminate after the hook is finished, though.
- People whose use cases (allegedly) allows recovery of bugs
I would consider these as niche cases that are already covered by the preexisting verbose syntax: `in { if (!cond) throw Exception(); }`. I would simply not include support for that in the easy-to-read variant.
- People who use assert (or other Errors) for user input validation
That's not what they are for.
To sum it up, I would like semantics similar to this (simplified) code for this syntax:
---
void delegate() nothrow onContractViolation;
template validateContract(T) if (isInputContract!T || isOutputContract!T)
{
version (ValidateContracts) void violated(ref T contract)
{
import core.stdc.stdlib : _Exit;
debug printDebugInfo(contract);
else printFileAndLIne(contract);
if (onContractViolation !is null) onContractViolation();
_Exit(1); // without scope guard since onContractViolation is `nothrow`
}
static if (isInputContract!T) {
void validateContract(T contract)
{
version (ValidateContracts) if (!contract) violated(contract);
}
} else {
void validateContract(T contract, T.ResultType result)
{
version (ValidateContracts) if (!contract(result)) violated(contract);
}
}
}
---
|
June 21, 2017 Re: DIP 1009--Improve Contract Usability--Preliminary Review Round 1 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Moritz Maxeiner | On Wednesday, 21 June 2017 at 11:31:41 UTC, Moritz Maxeiner wrote:
> [...]
Sorry for double post, please ignore this one.
|
June 21, 2017 Re: DIP 1009--Improve Contract Usability--Preliminary Review Round 1 | ||||
---|---|---|---|---|
| ||||
Posted in reply to meppl | On Wednesday, 21 June 2017 at 09:53:40 UTC, meppl wrote:
> On Wednesday, 21 June 2017 at 09:27:20 UTC, MysticZach wrote:
>> On Wednesday, 21 June 2017 at 08:15:34 UTC, MysticZach wrote:
>>> On Wednesday, 21 June 2017 at 04:16:22 UTC, Moritz Maxeiner wrote:
>>>> int myFunc(Args...)(Args args)
>>>> if (Args.length > 2)
>>>> in (args[0] != 0)
>>>> in (args[1] > 1)
>>>> out (result => result > 0) { ... }
>
> avoiding the "anonymous scope"-extra wouldnt hurt much?
>
> int myFunc(Args...)(Args args)
> if (Args.length > 2)
> in (args[0] != 0)
> in (args[1] > 1)
> out (result => result > 0)
> do { ... }
Adding `if (...)` should not be different from adding `in (...)` or `out (...)` in terms of syntax rules: it's inconsistent. If you want to have that `do` there, I would argue that it should also become required if only an `if (...)` is present, so
---
int myFunc(Args...)(Args args)
if (Args.length > 2)
{ ... }
---
should then become illegal and must be rewritten as
---
int myFunc(Args...)(Args args)
if (Args.length > 2)
do { ... }
---
I doubt that's going to happen, though (too much code breakage), and I also don't like it. Simply drop the `do`.
|
June 21, 2017 Re: DIP 1009--Improve Contract Usability--Preliminary Review Round 1 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Moritz Maxeiner | On Wednesday, 21 June 2017 at 12:05:55 UTC, Moritz Maxeiner wrote:
> On Wednesday, 21 June 2017 at 09:53:40 UTC, meppl wrote:
>> On Wednesday, 21 June 2017 at 09:27:20 UTC, MysticZach wrote:
>>> On Wednesday, 21 June 2017 at 08:15:34 UTC, MysticZach wrote:
>>>> On Wednesday, 21 June 2017 at 04:16:22 UTC, Moritz Maxeiner wrote:
>>>>> int myFunc(Args...)(Args args)
>>>>> if (Args.length > 2)
>>>>> in (args[0] != 0)
>>>>> in (args[1] > 1)
>>>>> out (result => result > 0) { ... }
>>
>> avoiding the "anonymous scope"-extra wouldnt hurt much?
>>
>> int myFunc(Args...)(Args args)
>> if (Args.length > 2)
>> in (args[0] != 0)
>> in (args[1] > 1)
>> out (result => result > 0)
>> do { ... }
>
> Adding `if (...)` should not be different from adding `in (...)` or `out (...)` in terms of syntax rules: it's inconsistent. If you want to have that `do` there, I would argue that it should also become required if only an `if (...)` is present, so
>
> ---
> int myFunc(Args...)(Args args)
> if (Args.length > 2)
> { ... }
> ---
>
> should then become illegal and must be rewritten as
>
> ---
> int myFunc(Args...)(Args args)
> if (Args.length > 2)
> do { ... }
> ---
>
> I doubt that's going to happen, though (too much code breakage), and I also don't like it. Simply drop the `do`.
yeah, i was probably not thinking too carefully about it. My idea was to keep the code readable, if the contracts are long. but as long as the "do" must appear behind a '}', everything is still fine, more or less.
int myFunc1(Args...)(Args args)
if (Args.length > 2)
in
{
// much code
}
do
{
...
}
int myFunc2(Args...)(Args args)
if (Args.length > 2)
in
{
// much code
}
out (result => result > 0)
{
...
}
both are readable, but one time we write `do` and the other time we dont. furthermore the second function body looks a little bit like belonging to "out". someone who is learning the D-language might get confused.
|
June 21, 2017 Re: DIP 1009--Improve Contract Usability--Preliminary Review Round 1 | ||||
---|---|---|---|---|
| ||||
Posted in reply to meppl | On Wednesday, 21 June 2017 at 12:43:46 UTC, meppl wrote:
>
> yeah, i was probably not thinking too carefully about it. My idea was to keep the code readable, if the contracts are long. but as long as the "do" must appear behind a '}', everything is still fine, more or less.
>
> both are readable, but one time we write `do` and the other time we dont. furthermore the second function body looks a little bit like belonging to "out". someone who is learning the D-language might get confused.
Oh, I hadn't thought about mixing the two syntax forms. I would just forbid it. Your signature then uses *either* the verbose form, *or* the compact form.
|
June 21, 2017 Re: DIP 1009--Improve Contract Usability--Preliminary Review Round 1 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Moritz Maxeiner | On Wednesday, 21 June 2017 at 12:05:55 UTC, Moritz Maxeiner wrote:
> On Wednesday, 21 June 2017 at 09:53:40 UTC, meppl wrote:
>> On Wednesday, 21 June 2017 at 09:27:20 UTC, MysticZach wrote:
>>> On Wednesday, 21 June 2017 at 08:15:34 UTC, MysticZach wrote:
>>>> On Wednesday, 21 June 2017 at 04:16:22 UTC, Moritz Maxeiner
> Adding `if (...)` should not be different from adding `in (...)` or `out (...)` in terms of syntax rules: it's inconsistent. If you want to have that `do` there, I would argue that it should also become required if only an `if (...)` is present, so
>
> ---
> int myFunc(Args...)(Args args)
> if (Args.length > 2)
> { ... }
> ---
>
> should then become illegal and must be rewritten as
>
> ---
> int myFunc(Args...)(Args args)
> if (Args.length > 2)
> do { ... }
> ---
>
> I doubt that's going to happen, though (too much code breakage), and I also don't like it. Simply drop the `do`.
I tend to agree. I think the grammar for `out` contracts is still murky, though, because the normal existing case is:
OutStatement:
out ( Identifier ) { OutContractsBody }
out { OutContractsBody }
My fix would be to require two sets of parentheses for the new conditional, like so:
OutStatement:
...
// new version
out ( Identifier ) ( IfCondition )
out ( ) ( IfCondition )
This makes the grammar unambiguous and clean. And because normally `out` contracts want to check the return value, the last case, `out ( ) ( ... )` will be the rare one. If you do accidentally forget the extra set of parens on the `out` contract, you would get "Error: `do` expected before function body after a bracketed `out` contract" at the end of the function.
(If, however, it a happens to be a nested function, and the next statement in that function happens to be `do`, then the parser will think the `do` loop is the function body... Mmmm, is this worth worrying about??)
|
June 21, 2017 Re: DIP 1009--Improve Contract Usability--Preliminary Review Round 1 | ||||
---|---|---|---|---|
| ||||
Posted in reply to MysticZach | On Wednesday, 21 June 2017 at 09:10:33 UTC, MysticZach wrote: > On Wednesday, 21 June 2017 at 05:19:26 UTC, H. S. Teoh wrote: >> Umm... I think we're not quite on the same page here. What *else* are people supposed to use inside their contracts besides the built-in assert?? > > I believe `assert` would have to be extremely robust to merit being included directly into the syntax of the language. I'm not opposed to this in principle. But I'm no expert, and not willing to assume it's desirable. On the other hand, if `assert` were made so perfect as to ensure that no one would prefer a different method of bailing out of their programs, then you're right, and the problem of contract syntax could be solved at that level instead of the more "pedestrian" approach I'm taking. So weird how this discussion is happening in parallel with this other discussion :-) : http://forum.dlang.org/post/rkdpuuggltowhqmcmmke@forum.dlang.org |
June 21, 2017 Re: DIP 1009--Improve Contract Usability--Preliminary Review Round 1 | ||||
---|---|---|---|---|
| ||||
Posted in reply to MysticZach | On Wednesday, 21 June 2017 at 13:11:10 UTC, MysticZach wrote: > [...] > > My fix would be to require two sets of parentheses for the new conditional, like so: > > OutStatement: > ... > // new version > out ( Identifier ) ( IfCondition ) > out ( ) ( IfCondition ) > > This makes the grammar unambiguous and clean. And because normally `out` contracts want to check the return value, the last case, `out ( ) ( ... )` will be the rare one. If I read that correctly as --- int myFunc(Args...)(Args args) if (Args.length > 2) in (args[0] != 0) in (args[1] > 1) out (result)(result > 0) { ... } --- then yeah, I would be happy with that, as well (and would love for the DIP to do this instead). > If you do accidentally forget the extra set of parens on the `out` contract, you would get "Error: `do` expected before function body after a bracketed `out` contract" at the end of the function. > > (If, however, it a happens to be a nested function, and the next statement in that function happens to be `do`, then the parser will think the `do` loop is the function body... Mmmm, is this worth worrying about??) Could you give a specific (short) example of what you think of? I don't see any potential for ambiguity in the following at a quick glance, so I'm not sure I got where you think the problem lies: --- void foo() { int bar(Args...)(Args args) if (Args.length > 2) in (args[0] != 0) in (args[1] > 1) out (result)(result > 0) { ... } do {} while (true); } --- |
Copyright © 1999-2021 by the D Language Foundation