Something I think important in this kind of discussion is a bit more thought to what to do if your preconditions fail.
I’ve seen code in the past which seems to have evolved along the lines of:
use this arg passed in
have a crash report, arg was null
wrap function body in “if (arg != null) { … }” (with no ‘else’)
crash fixed, yay - commit and move on
Now rearranging that as a guard clause gets rid of one annoyance (code indent etc) but if the action taken if the guard clause fails is just “return”, we’ve missed the opportunity to fix something.
If some of the args don’t meet some criteria, that might be more reasonably an exception or panic() - it could well be a logic error in some other part of the program. Some times it makes sense for a function to be a no-op, but it’s pretty rare (the ‘draft timer’ in the article).
When a failed guard condition raises an immediate failure, that’s called a contract precondition. The general rule I’m familiar with is “return and clean up if the guard might be false, throw an error if the guard shouldn’t be false.”
You’re right, in most cases you’ll need to throw in a guard clause instead of return. You only return when failed preconditions means you don’t need to do any actions, not can’t do. Like deleting a resource when it doesn’t exist.
This makes me wonder what blessed syntax would be for a positive assertion equivalent of an if-statement would be. ensure timer != null has a taste to it…
Might place more requirements on both lang and lib than you were asking for, but I’ve been messing with a reterr (return error) keyword in my language Tiko that works alot like Rust’s try! macro/? operator.
Tiko uses an Option instead of null, so
func doThing(x Option<T>) {
reterr x
fmt.Println("only reached if not null!")
}
desugars into
func doThing(x Option<T>) {
match x {
None => return
}
fmt.Println("only reached if not null")
}
reterr returns on any falsey expression, any Result<T, E> that is an Error, and any empty Option<T>. Anything else continues through. It will also pass the error up from Result iff the func’s type signature returns an Error.
I like it alot. It makes for some VERY clean code that reads declaratively but doesn’t seem out of place in the imperative style. Preconditions and Go-like if err != nil { return err } handling are readable but don’t take up the majority of visual space on screen.
I worry because it does quite alot of things. It blesses Result and Option, plus it just happens to work with falsey expressions which aren’t necessarily errors. It’s got utility, but not exactly the greatest in terms of aesthetics from a language design standpoint. reterr and return are also super similar visually and might be confused/missed. Since this is mainly a toy language it’s mostly just me having fun and I haven’t had much issue with it so maybe I’m just too hesitant to commit ;)
It’s definitely not a new idea (mostly ripped off from rejected ideas in the Rust ? operator RFC) but given your comment I thought it might be interesting enough to merit a reply.
Some languages (like lisps/schemes) have when and unless. (when foo bar ...) is similar to if (foo) { bar ... }, (unless foo bar ...) is like if (foo) {} else { bar ... }.
Yeah, I’m solidly on the “unless is a useful keyword camp” in Ruby land. I kinda want something that combines that with a return or raise or something though… /u/badtuple has some neat thoughts below.
I personally like unless keyword, though unfortunately it’s not present in Java. Also one I really liked (maybe most) is a pattern-matching-like approach I saw in Erlang:
execute_timer(entity) when entity /= null ->
sound_alarm(entity);
execute_timer(_) ->
do_nothing.
Ah yeah, I’m most familiar with pattern matching in Scala and agree it’s pretty rad, but the Erlang way seems better in some ways since it groups cases together from a definition standpoint (?). I think Haskell does that too, and it makes for really clean stuff there.
Something I think important in this kind of discussion is a bit more thought to what to do if your preconditions fail.
I’ve seen code in the past which seems to have evolved along the lines of:
Now rearranging that as a guard clause gets rid of one annoyance (code indent etc) but if the action taken if the guard clause fails is just “return”, we’ve missed the opportunity to fix something.
If some of the args don’t meet some criteria, that might be more reasonably an exception or panic() - it could well be a logic error in some other part of the program. Some times it makes sense for a function to be a no-op, but it’s pretty rare (the ‘draft timer’ in the article).
When a failed guard condition raises an immediate failure, that’s called a contract precondition. The general rule I’m familiar with is “return and clean up if the guard might be false, throw an error if the guard shouldn’t be false.”
You’re right, in most cases you’ll need to
throw
in a guard clause instead of return. You onlyreturn
when failed preconditions means you don’t need to do any actions, not can’t do. Like deleting a resource when it doesn’t exist.This makes me wonder what blessed syntax would be for a positive assertion equivalent of an if-statement would be.
ensure timer != null
has a taste to it…Might place more requirements on both lang and lib than you were asking for, but I’ve been messing with a
reterr
(return error) keyword in my language Tiko that works alot like Rust’stry!
macro/?
operator.Tiko uses an Option instead of
null
, sodesugars into
reterr
returns on any falsey expression, anyResult<T, E>
that is an Error, and any emptyOption<T>
. Anything else continues through. It will also pass the error up from Result iff the func’s type signature returns an Error.I like it alot. It makes for some VERY clean code that reads declaratively but doesn’t seem out of place in the imperative style. Preconditions and Go-like
if err != nil { return err }
handling are readable but don’t take up the majority of visual space on screen.I worry because it does quite alot of things. It blesses Result and Option, plus it just happens to work with falsey expressions which aren’t necessarily errors. It’s got utility, but not exactly the greatest in terms of aesthetics from a language design standpoint.
reterr
andreturn
are also super similar visually and might be confused/missed. Since this is mainly a toy language it’s mostly just me having fun and I haven’t had much issue with it so maybe I’m just too hesitant to commit ;)It’s definitely not a new idea (mostly ripped off from rejected ideas in the Rust
?
operator RFC) but given your comment I thought it might be interesting enough to merit a reply.That’s rad! cheers! I imagine it might end up looking a little Eifel like eventually with a neat stack of precondition reterrs at the beginning.
Some languages (like lisps/schemes) have
when
andunless
.(when foo bar ...)
is similar toif (foo) { bar ... }
,(unless foo bar ...)
is likeif (foo) {} else { bar ... }
.Yeah, I’m solidly on the “
unless
is a useful keyword camp” in Ruby land. I kinda want something that combines that with a return or raise or something though… /u/badtuple has some neat thoughts below.ensure
is probably one of the better terms to use for this. I might steal this!Another one you might like is
require
for preconditions.require
for preconditions andensure
for postconditions is lovely!I think
require
may be ambiguous in JS land :)I personally like
unless
keyword, though unfortunately it’s not present in Java. Also one I really liked (maybe most) is a pattern-matching-like approach I saw in Erlang:Ah yeah, I’m most familiar with pattern matching in Scala and agree it’s pretty rad, but the Erlang way seems better in some ways since it groups cases together from a definition standpoint (?). I think Haskell does that too, and it makes for really clean stuff there.
Swift has the guard statement.
Here is a rough translation of the function in the linked article.