1. 1

    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…

    1. 2

      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.

      1. 2

        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.

      2. 2

        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 ... }.

        1. 2

          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.

        2. 2

          ensure is probably one of the better terms to use for this. I might steal this!

          1. 3

            Another one you might like is require for preconditions.

            1. 2

              require for preconditions and ensure for postconditions is lovely!

              1. 1

                I think require may be ambiguous in JS land :)

            2. 2

              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.
              
              1. 2

                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.

              2. 1

                Swift has the guard statement.

                Here is a rough translation of the function in the linked article.

                func run(timer : Timer) throws
                {
                    guard timer.enabled else {
                        return
                    }
                    guard timer.valid else {
                        throw InvalidTimerException()
                    }
                    timer.run()
                }
                
              1. 3

                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).

                1. 1

                  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.”

                  1. 1

                    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.