You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The |node| is required, because if it wasn't needed then it could just read while (it.next() != null) {} as a normal boolean while loop.
If we follow this pattern, we can remove test for nullable types and use if in this same way. If the if statement has the |payload| then it's doing nullable unwrapping.
What about error types? Perhaps if we do the above then if and while should work with error union types as well. This has the downside of slightly less type safety.
I'd be very happy to see if work the same way. test always felt like an awkward word for this purpose.
As for the error union types, you would only lose type safety in the else block, and only if you try to use the null value as the error value. A simple fix would be to only allow a | val | for the else block if the original value is an error union.
In other words:
if ( err_union ) | payload | { } else | err | {} // OK
if ( err_union ) | payload | { } else {} // OK
if ( maybe_T ) | payload | { } else | null_T | {} // Not OK
if ( maybe_T ) | payload | { } else {} // OK
One idea which would solve the type safety issue is for error unions we could require the else |err| (even if you do end up doing else |_|). Requiring this is similar to enforcing that you check for the error and at least acknowledge that there was one in the syntax.
Exactly, and notice that it's a perfect XOR, e.g. you can't accidentally do something with a nullable when you meant to use an error union, or vice versa.
(I started typing this when there were only 2 comments in this thread.)
The Java way to treat if is that only booleans are allowed. The Python way to treat if is that all types of things should be able to work as a boolean somehow. I'm extremely biased in favor of the Java approach. Even in C++, I don't like if (ptr) instead of if (ptr != nullptr).
When I look at code, I like to be able to know what the types of things are. If I see if (x) in Java, I know x is a boolean (or Boolean). If I see if x: in Python, I have no idea what's going on. x could be a bool, int, str, list, etc. and the programmer may have meant something meaningful in each of those cases, like if x != False:, if x != 0:, if len(x) != 0:, etc., or in any case if x != None:. This is somehow sold as a "feature" of Python, where it's easy to write "powerful" or "expressive" code or some shit, but it just ends up being confusing (and even error prone) in a language that supposedly emphasizes readability.
The above emotions are a big motivator for Zig's boolean-only rule in if conditions. I know that @andrewrk isn't quite as enthusiastic about this particular point as I am. The point of bringing all this up is that I want these concerns to be considered, and to be considered with a grain of salt.
This is all relevant to this issue because I don't want there to be a syntax that works for two (or a finite number of) different types without being clear which one it's operating on.
If I see while (a.foo()) { }, I want to be sure that foo() is returning a bool.
If I see if (x) { }, I want to be sure that x is a bool.
Since starting to type this rant, I see some cool proposals being discussed that perfectly fit my readability objective. Here's a list of if syntaxes from the perspective of type readability:
if (a) {} - a is bool.
if (a) {} else {} - a is bool.
if (a) |b| {} - a is ?@typeOf(b).
if (a) |b| {} else {} - a is ?@typeOf(b).
if (a) |b| {} else |c| {} - a is %@typeOf(b).
if (a) {} else |b| {} - a is %void.
And here's the while syntaxes following the proposal in the OP:
while (a) {} - a is bool.
while (a) |b| {} - a is ?@typeOf(b).
Here are some concerns:
if (a) {} else |b| {} might take a lot of scrolling to find the |b| to inform your interpretation of the type of a.
using if on an error type means I have to put the success handler before the error handler ("then" before else), which might not be what I want, like If I wanted to return on error. But maybe in this case I can use %% to handle the error, and return. Maybe this is ok.
using if on an error type means I have to put the success handler before the error handler ("then" before else), which might not be what I want, like If I wanted to return on error. But maybe in this case I can use %% to handle the error, and return. Maybe this is ok.
In theory, you could evaluate the extra expression on the while loop in the scope of the block - where the node is unwrapped.
Right, but that would look incorrect. It would be the only place syntactically that you don't read left to right, top to bottom, to determine what's in scope.
Here's another proposal:
Instead of
while (condition; expression) {}
have
while (condition) : (expression) {}
and the : (expression) is optional. We need the parens for the same reason we need the parens for if, while, for, etc., which is that symbol { } is struct initialization syntax.
Then the example becomes:
varmaybe_node=linked_list.first;
while (maybe_node) |node| : (maybe_node=node.next) {
// ...
}
A loop to count to 10 looks like:
varn: usize=1;
while (n<=10) : (n+=1) {
%%io.stderr.printf("{}\n", n);
}
I quite like this as well, ; is usually associated with the termination of a statement so I think it would look out of place here. However, I don't like the idea of using : here because the colon usually goes next to a type or a label (see also #346). Could we use the do keyword?
while (n <= 10) do (n += 1) {}
while (maybe_node) |node| do (maybe_node = node.next) {}
For completeness, a regular do-while would use this, but you wouldn't be able to unwrap with a do-while.
var n: usize = 1;
do {
%%io.stderr.printf("{}\n", n);
} while (n <= 10) : (n += 1);
// Above is fine, null unwrap is weird. Fine because it makes no sense not to unwrap.
var maybe_node = linked_list.first;
do {
// node not defined
} while (maybe_node) |node| : (maybe_node = node.next);
Just to throw in the ol' josh-style super verbose but pretty readable and correct syntax proposal:
while (a) continue: (b) {}
while (a) |b|continue: (c) {}
here it looks like you're declaring a label called continue, which is pretty reasonable. that's a lot of typing though.
I'm not a fan of the while-do proposal, because it looks like the do clause is what gets executed every iteration. similar to Bash's for a in b; do c; done, or the way you'd use do and done in Ruby (i think).
I agree this continue: syntax is readable and correct. I feel like we're already kind of walking on thin ice here with verbosity and while (a) : (b) {} is a reasonable compromise.
Now that while expressions have else nodes, it might make sense to have break take an expression parameter like return. So you can have something like:
Activity
andrewrk commentedon May 3, 2017
A few notes:
|node|is required, because if it wasn't needed then it could just readwhile (it.next() != null) {}as a normal boolean while loop.testfor nullable types and useifin this same way. If theifstatement has the|payload|then it's doing nullable unwrapping.ifandwhileshould work with error union types as well. This has the downside of slightly less type safety.raulgrell commentedon May 3, 2017
I'd be very happy to see
ifwork the same way.testalways felt like an awkward word for this purpose.As for the error union types, you would only lose type safety in the else block, and only if you try to use the null value as the error value. A simple fix would be to only allow a
| val |for the else block if the original value is an error union.In other words:
andrewrk commentedon May 3, 2017
One idea which would solve the type safety issue is for error unions we could require the
else |err|(even if you do end up doingelse |_|). Requiring this is similar to enforcing that you check for the error and at least acknowledge that there was one in the syntax.raulgrell commentedon May 3, 2017
That would mean:
andrewrk commentedon May 3, 2017
Exactly, and notice that it's a perfect XOR, e.g. you can't accidentally do something with a nullable when you meant to use an error union, or vice versa.
thejoshwolfe commentedon May 3, 2017
(I started typing this when there were only 2 comments in this thread.)
The Java way to treat
ifis that only booleans are allowed. The Python way to treatifis that all types of things should be able to work as a boolean somehow. I'm extremely biased in favor of the Java approach. Even in C++, I don't likeif (ptr)instead ofif (ptr != nullptr).When I look at code, I like to be able to know what the types of things are. If I see
if (x)in Java, I knowxis aboolean(orBoolean). If I seeif x:in Python, I have no idea what's going on.xcould be abool,int,str,list, etc. and the programmer may have meant something meaningful in each of those cases, likeif x != False:,if x != 0:,if len(x) != 0:, etc., or in any caseif x != None:. This is somehow sold as a "feature" of Python, where it's easy to write "powerful" or "expressive" code or some shit, but it just ends up being confusing (and even error prone) in a language that supposedly emphasizes readability.The above emotions are a big motivator for Zig's boolean-only rule in
ifconditions. I know that @andrewrk isn't quite as enthusiastic about this particular point as I am. The point of bringing all this up is that I want these concerns to be considered, and to be considered with a grain of salt.This is all relevant to this issue because I don't want there to be a syntax that works for two (or a finite number of) different types without being clear which one it's operating on.
while (a.foo()) { }, I want to be sure thatfoo()is returning abool.if (x) { }, I want to be sure thatxis abool.Since starting to type this rant, I see some cool proposals being discussed that perfectly fit my readability objective. Here's a list of
ifsyntaxes from the perspective of type readability:if (a) {}-aisbool.if (a) {} else {}-aisbool.if (a) |b| {}-ais?@typeOf(b).if (a) |b| {} else {}-ais?@typeOf(b).if (a) |b| {} else |c| {}-ais%@typeOf(b).if (a) {} else |b| {}-ais%void.And here's the
whilesyntaxes following the proposal in the OP:while (a) {}-aisbool.while (a) |b| {}-ais?@typeOf(b).Here are some concerns:
if (a) {} else |b| {}might take a lot of scrolling to find the|b|to inform your interpretation of the type ofa.ifon an error type means I have to put the success handler before the error handler ("then" beforeelse), which might not be what I want, like If I wanted to return on error. But maybe in this case I can use%%to handle the error, and return. Maybe this is ok.andrewrk commentedon May 3, 2017
This is status quo.
One more syntax that was proposed:
while (a) |b| {} else |c| {}-ais%@typeOf(b).while (a) {} else |b| {}-ais%void.andrewrk commentedon May 3, 2017
Consider this code:
You should be able to put the
maybe_node = node.nextas the extra expression in the while loop, but it doesn't work because of scoping:Not sure how to solve this.
AndreaOrru commentedon May 3, 2017
In theory, you could evaluate the extra expression on the while loop in the scope of the block - where
nodeis unwrapped.andrewrk commentedon May 3, 2017
Right, but that would look incorrect. It would be the only place syntactically that you don't read left to right, top to bottom, to determine what's in scope.
Here's another proposal:
Instead of
while (condition; expression) {}have
while (condition) : (expression) {}and the
: (expression)is optional. We need the parens for the same reason we need the parens forif,while,for, etc., which is thatsymbol { }is struct initialization syntax.Then the example becomes:
A loop to count to 10 looks like:
Maybe
;instead of:?AndreaOrru commentedon May 3, 2017
I actually really like this. And
:is better IMHO.raulgrell commentedon May 3, 2017
I quite like this as well,
;is usually associated with the termination of a statement so I think it would look out of place here. However, I don't like the idea of using:here because the colon usually goes next to a type or a label (see also #346). Could we use the do keyword?For completeness, a regular do-while would use this, but you wouldn't be able to unwrap with a do-while.
andrewrk commentedon May 3, 2017
dolooks good as a replacement for:, but I don't think it makes sense to have that role there, and then a different role in ado...whilestatement.I'm not a fan of do...while, because it's barely different than:
I feel like do...while is trying to bend the syntax over backwards to accommodate what can be accomplished with a more explicit control flow.
remove test and try expressions in favor of if expressions
thejoshwolfe commentedon May 4, 2017
Just to throw in the ol' josh-style super verbose but pretty readable and correct syntax proposal:
here it looks like you're declaring a label called
continue, which is pretty reasonable. that's a lot of typing though.I'm not a fan of the while-do proposal, because it looks like the do clause is what gets executed every iteration. similar to Bash's
for a in b; do c; done, or the way you'd usedoanddonein Ruby (i think).change while syntax
andrewrk commentedon May 4, 2017
I agree this
continue:syntax is readable and correct. I feel like we're already kind of walking on thin ice here with verbosity andwhile (a) : (b) {}is a reasonable compromise.andrewrk commentedon May 4, 2017
Now that
whileexpressions haveelsenodes, it might make sense to havebreaktake an expression parameter likereturn. So you can have something like:implement while for nullables and error unions