www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Wanted: best way to express default expectations

reply Dukc <ajieskola gmail.com> writes:
This is a very common pattern:

```
auto sumSquares(R)(R values)
     if (isInputRange!R && isPointer!(ElementType!R) && 
isIntegral!(typeof(*values.front)))
{   typeof(*values.front) result = 0;
     foreach (value; values)
     {	if (value is null) continue;
      	result += *value * *value;
     }
     return result;
}
```

`values` may obviously contain null values, which have to be 
taken account in the body of the `foreach` loop.

The problem here is that `if (value is null)` is stating the 
condition rest of the code block does NOT want. This is exactly 
the opposite behaviour of `assert`, `enforce` and `in` 
statements, and will make - I think - maintenance-induced bugs 
more likely.

You can't just add a library solution in conventional D style, 
because a lambda you would pass to `alias` template parameter 
could not `continue` the foreach loop (nor `return`, `break` or 
`goto`).

a '!' mark to negate the `if` condition works, but at least for 
my eye, it would be more cognitive load to read such a condition, 
especially if it has and/or statements that have to be negated as 
whole.

In this case, the best solution would doubtlessly be this:
```
foreach (value; values) if (value !is null)
{   result += *value * *value;
}
```

...however this is a bad general solution. Often, you have many 
expectations that must be stated at different parts of the code 
block. This solution to those cases would mean very deep nesting, 
and a lot of short `else` statements at the end of the function. 
Not ideal.

Perhaps the best general solution I can come up with is `if 
(value !is null){} else continue`. It states the default 
expectation instead of it's negation value, and handles the 
exceptional case right away. Still, quickly looking, it looks a 
lot like of an `if` statement that is meant to branch the code 
block, not just terminate it early, and thus i think the negation 
(`{} else`) might be missed more easily than with e.g. `assert`s.

Note that this does not only apply to loops. It's just as common 
to shortcut-return from a function in cases where rest of the 
logic is needless.

What do you think? Is there a good possiblility to do some sort 
of library solution? Or would a DIP be more appropriate? Or is my 
taste just a bit strange here?
Oct 10
parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Thursday, 10 October 2019 at 11:34:44 UTC, Dukc wrote:
 This is a very common pattern:

 ```
 auto sumSquares(R)(R values)
     if (isInputRange!R && isPointer!(ElementType!R) && 
 isIntegral!(typeof(*values.front)))
 {   typeof(*values.front) result = 0;
     foreach (value; values)
     {	if (value is null) continue;
      	result += *value * *value;
     }
     return result;
 }
 ```
 What do you think? Is there a good possiblility to do some sort 
 of library solution?
https://dlang.org/phobos/std_algorithm_iteration.html#.filter
Oct 10
parent reply Dukc <ajieskola gmail.com> writes:
On Thursday, 10 October 2019 at 11:44:58 UTC, Nicholas Wilson 
wrote:
 https://dlang.org/phobos/std_algorithm_iteration.html#.filter
For loops, that's an excellent choice. But what about early function returns?
Oct 10
parent reply Dukc <ajieskola gmail.com> writes:
On Thursday, 10 October 2019 at 11:47:21 UTC, Dukc wrote:
 On Thursday, 10 October 2019 at 11:44:58 UTC, Nicholas Wilson 
 wrote:
 https://dlang.org/phobos/std_algorithm_iteration.html#.filter
For loops, that's an excellent choice. But what about early function returns?
And also, sometimes it's not practical to filter the range up front. You may also want to shortcut out in middle of the `foreach` body, or `break` out of an outer loop, or `return` instead of just aborting the loop.
Oct 10
next sibling parent reply Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Thursday, 10 October 2019 at 11:50:59 UTC, Dukc wrote:
 On Thursday, 10 October 2019 at 11:47:21 UTC, Dukc wrote:
 On Thursday, 10 October 2019 at 11:44:58 UTC, Nicholas Wilson 
 wrote:
 https://dlang.org/phobos/std_algorithm_iteration.html#.filter
For loops, that's an excellent choice. But what about early function returns?
And also, sometimes it's not practical to filter the range up front. You may also want to shortcut out in middle of the `foreach` body, or `break` out of an outer loop, or `return` instead of just aborting the loop.
https://dlang.org/phobos/std_algorithm_iteration.html#.each allows early stopping by having the iteration function return No.each to stop the iteration, but I'm not sure whether this is the thing you're looking for. If you're trying to solve a more general problem that would require the ability to return early from a nested scope I would also suggest checking opApply (may work for a simpler scenario) and Fibers, which when applied well can provide a substantial increase in expressive power.
Oct 10
parent Dukc <ajieskola gmail.com> writes:
On Thursday, 10 October 2019 at 13:36:05 UTC, Petar Kirov 
[ZombineDev] wrote:
 https://dlang.org/phobos/std_algorithm_iteration.html#.each 
 allows early stopping by having the iteration function return 
 No.each to stop the iteration, but I'm not sure whether this is 
 the thing you're looking for.
 If you're trying to solve a more general problem that would 
 require the ability to return early from a nested scope I would 
 also suggest checking opApply (may work for a simpler scenario) 
 and Fibers, which when applied well can provide a substantial 
 increase in expressive power.
These make possible to emulate `break` for the innermost range, but still they only with loops (not try blocks or the whole function body for example). And even in case of ranges, they don't let one to `return`, `goto` or `break`/`continue` an outer loop directly from the loop body. And no, I'm not looking for a way to do it -an `if` statement can do that like the first example shows. More like, i'm thinking an `if` equivalent that'll execute the statement when NOT true. Basically it would be like `assert`, except it would exit a code block instead of terminating the program when it fails.
Oct 10
prev sibling parent reply Jesse Phillips <Jesse.K.Phillips+D gmail.com> writes:
On Thursday, 10 October 2019 at 11:50:59 UTC, Dukc wrote:
 On Thursday, 10 October 2019 at 11:47:21 UTC, Dukc wrote:
 On Thursday, 10 October 2019 at 11:44:58 UTC, Nicholas Wilson 
 wrote:
 https://dlang.org/phobos/std_algorithm_iteration.html#.filter
For loops, that's an excellent choice. But what about early function returns?
And also, sometimes it's not practical to filter the range up front. You may also want to shortcut out in middle of the `foreach` body, or `break` out of an outer loop, or `return` instead of just aborting the loop.
Ignore the result has no meaning import std.stdio; void main() { import std.algorithm; import std.range; iota(10)   .filter!(x => x%2)   .until!(x => x>5)    .map!"a * a"     .filter!(x => x%4).writeln; }
Oct 12
parent reply Jesse Phillips <Jesse.K.Phillips+D gmail.com> writes:
On Saturday, 12 October 2019 at 16:24:01 UTC, Jesse Phillips 
wrote:
 On Thursday, 10 October 2019 at 11:50:59 UTC, Dukc wrote:
 On Thursday, 10 October 2019 at 11:47:21 UTC, Dukc wrote:
 On Thursday, 10 October 2019 at 11:44:58 UTC, Nicholas Wilson 
 wrote:
 https://dlang.org/phobos/std_algorithm_iteration.html#.filter
For loops, that's an excellent choice. But what about early function returns?
And also, sometimes it's not practical to filter the range up front. You may also want to shortcut out in middle of the `foreach` body, or `break` out of an outer loop, or `return` instead of just aborting the loop.
Sorry forgot the foreach break part. import std.stdio; void main() { import std.algorithm; import std.range; foreach(i; iota(10)   .filter!(x => x%2)   .until!(x => x>5)    .map!"a * a"     .filter!(x => x%4)) {    i.writeln;    break;   } } Note this is all done lazily so there is no walking of the iota which happens.
Oct 12
next sibling parent Dukc <ajieskola gmail.com> writes:
On Saturday, 12 October 2019 at 16:48:56 UTC, Jesse Phillips 
wrote:
 Sorry forgot the foreach break part.

 import std.stdio;
 void main()
 {
 import std.algorithm;
 import std.range;

 foreach(i; iota(10)
     .filter!(x => x%2)
     .until!(x => x>5)
     .map!"a * a"
     .filter!(x => x%4)) {
     i.writeln;
     break;
    }
 }

 Note this is all done lazily so there is no walking of the iota 
 which happens.
This is not exactly what I wanted, but probably the best alternative overall. At least it's better than in C#, as you don't sacrifice as much performance for using functional pipelines. But I still think an `if_not`, `ifnot` or some similar keyword would have it's place. Does anybody agree? Read: might it be worth a DIP?
Oct 14
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Saturday, 12 October 2019 at 16:48:56 UTC, Jesse Phillips 
wrote:
 Sorry forgot the foreach break part.

 import std.stdio;
 void main()
 {
 import std.algorithm;
 import std.range;

 foreach(i; iota(10)
     .filter!(x => x%2)
     .until!(x => x>5)
     .map!"a * a"
     .filter!(x => x%4)) {
     i.writeln;
     break;
    }
 }

 Note this is all done lazily so there is no walking of the iota 
 which happens.
This is not exactly what I wanted, but probably the best alternative overall. At least it's better than in C#, as you don't sacrifice as much performance for using functional pipelines. But I still think an `if_not`, `ifnot` or some similar keyword would have it's place. Does anybody agree? Read: might it be worth a DIP?
Oct 14
next sibling parent reply Dennis <dkorpel gmail.com> writes:
On Monday, 14 October 2019 at 13:00:14 UTC, Dukc wrote:
 But I still think an `if_not`, `ifnot` or some similar keyword 
 would have it's place. Does anybody agree? Read: might it be 
 worth a DIP?
I think a new keyword for this has very little chance. One related idea is making `if !(expression) statement` allowed in the grammar (! outside parentheses) which makes it easier to read fully negated if-statements. It hasn't been that well received though: https://github.com/dlang/dmd/pull/8440 https://forum.dlang.org/post/pgkv3t$15te$1 digitalmars.com
Oct 14
parent Dukc <ajieskola gmail.com> writes:
On Monday, 14 October 2019 at 13:26:56 UTC, Dennis wrote:
 I think a new keyword for this has very little chance.
 One related idea is making `if !(expression) statement` allowed 
 in the grammar (! outside parentheses) which makes it easier to 
 read fully negated if-statements.
Would also do the trick imo, and also avoid a new keyword. Good idea.
 It hasn't been that well received though:

 https://github.com/dlang/dmd/pull/8440
 https://forum.dlang.org/post/pgkv3t$15te$1 digitalmars.com
Thanks for those links - I didn't know that `if !(condition)` is allowed in C#. That's useful.
Oct 16
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Monday, 14 October 2019 at 13:00:14 UTC, Dukc wrote:
 On Saturday, 12 October 2019 at 16:48:56 UTC, Jesse Phillips 
 wrote:
 Sorry forgot the foreach break part.

 import std.stdio;
 void main()
 {
 import std.algorithm;
 import std.range;

 foreach(i; iota(10)
     .filter!(x => x%2)
     .until!(x => x>5)
     .map!"a * a"
     .filter!(x => x%4)) {
     i.writeln;
     break;
    }
 }

 Note this is all done lazily so there is no walking of the 
 iota which happens.
This is not exactly what I wanted, but probably the best alternative overall. At least it's better than in C#, as you don't sacrifice as much performance for using functional pipelines.
Pretty sure you don't need the foreach loop in this example; you can just use `take`: iota(10) .filter!(x => x%2) // ... .take(1) .each!writeln;
Oct 14
prev sibling parent reply Jesse Phillips <Jesse.K.Phillips+D gmail.com> writes:
On Monday, 14 October 2019 at 13:00:14 UTC, Dukc wrote:
 On Saturday, 12 October 2019 at 16:48:56 UTC, Jesse Phillips 
 wrote:
 Sorry forgot the foreach break part.

 import std.stdio;
 void main()
 {
 import std.algorithm;
 import std.range;

 foreach(i; iota(10)
     .filter!(x => x%2)
     .until!(x => x>5)
     .map!"a * a"
     .filter!(x => x%4)) {
     i.writeln;
     break;
    }
 }

 Note this is all done lazily so there is no walking of the 
 iota which happens.
This is not exactly what I wanted, but probably the best alternative overall. At least it's better than in C#, as you don't sacrifice as much performance for using functional pipelines.
How is not what you want? I realize it doesn't use the same foreach break structure, but then there is no alternative.
 But I still think an `if_not`, `ifnot` or some similar keyword 
 would have it's place. Does anybody agree? Read: might it be 
 worth a DIP?
I don't think it is worth it and I do think the range based approach is direction people should head.
Oct 14
parent Dukc <ajieskola gmail.com> writes:
On Monday, 14 October 2019 at 15:26:03 UTC, Jesse Phillips wrote:
 How is not what you want? I realize it doesn't use the same 
 foreach break structure, but then there is no alternative.
I think you nailed it - there is apparently no (better) alternative currently. So it's down to discussing DIP or no, and if yes, what should it propose.
 But I still think an `if_not`, `ifnot` or some similar keyword 
 would have it's place. Does anybody agree? Read: might it be 
 worth a DIP?
I don't think it is worth it and I do think the range based approach is direction people should head.
After reading what Dennis wrote, I think I agree that relaxing parenthesis requirements for `if` statements (and perhaps `while`/`switch`/`with` while on it) is a better way to go. But while I agree that pursuing range pipelines is a good general diretion, sometimes it is just more practical to do stuff in the imperative way - the langauge should not discourage that.
Oct 16