This is more of a request for comments and not a complete proposal. I will try
to keep this short(ish) and simple.

Note that the `else` I will write about is not to be confused with the usual
`else` used with `if`. It is an `else` in a slightly different context.

`else` is basically an `if` with a variable initialized to the last return value
of a function call from the previous line.

Note: "from the previous line" is a simplification. Between the function call
and the `else` statement white space and comments are allowed, but nothing
else.

Another note: "function call" is also a simplification. It can be any statement
that returns a value. It could be, for example, the "comma ok" idiom used for
testing whether an entry in a map is present.

A few simple rules:

   - Only the last return value is ever taken, not multiple.
   - If there is no return value, `else` is not possible.
   - The variable is defined only within the scope of the `else` block.
   - The name of the variable can be any valid identifier.
   - Braces are mandatory.


Code like this:

f, err := os.Open("filename.ext")
if err != nil{
    log.Fatal(err)
}

would become:

f := os.Open("filename.ext")
else err != nil {
    log.Fatal(err)
}

The `err` variable in the example above is automatically initialized to the last
return value of the function call `os.Open`. The variable could have any valid
variable name and is defined only in the `else` block.

I like to read the example above with an implicit try before the function call.
I.e., try opening filename.ext, else if err is not nil... For this reason, I
call this construct else catch.

A longer example from Error Handling - Problem Overview 
<https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md>:

func CopyFile(src, dst string) error {
    r, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    defer r.Close()

    w, err := os.Create(dst)
    if err != nil {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }

    if _, err := io.Copy(w, r); err != nil {
        w.Close()
        os.Remove(dst)
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }

    if err := w.Close(); err != nil {
        os.Remove(dst)
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
}

would become:

func CopyFile(src, dst string) error {
    r := os.Open(src)
    else err != nil {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    defer r.Close()

    w := os.Create(dst)
    else err != nil {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }

    io.Copy(w, r)
    else err != nil {
        w.Close()
        os.Remove(dst)
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }

    w.Close()
    else err != nil {
        os.Remove(dst)
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
}

By using the else catch we declutter the function calls, more specifically
assignments, thus improving readability. Error handling stays familiar,
explicit, and simple.

And since the variable in else catch is defined only in the `else` block, we do
not need to re-assign the `err` variable again and again, making the code more
obvious and unambiguous.

I would like to note that the else catch example is longer, but I think length
should not be equated with readability.



Else catch is not meant to replace the `if err != nil` idiom, but rather
complement it. It would allow for a clear separation of return values we "care"
about and error handling. Or put another way, it would allow for a clear
separation of a happy and an unhappy path of execution.



What about the error handling blocks?

We should not try to stash away error handling. Most gophers are already used to
skipping error handling blocks while reading Go. Using else catch would make
that even easier. IDEs could collapse else catch blocks on command.



Else catch is orthogonal and can be used for anything, not just error handling.
We just have to remember that it always takes the last (and no more) return
value of a function call from the previous line.

For example, we can test for existence of a key:

m := map[string]int{
    "route": 66,
}
i := m["route"]
else !ok {
    i = -1
}



Now, there is a slight problem with current else catch. If it is basically an
`if`, what happens if we use multiple variables in the condition? Which variable
came from the last return value?

firstTimeBonus := true
apples := map[string]int{
    "bob": 7,
    ...
}
apples["john"]
else !ok && firstTimeBonus {
    apples["john"] = 3
}

In this case we can probably figure out that variable `ok` is the last return
value of `apples["john"]`. But it is not explicitly stated that `ok` came from
the last return value. What if `ok` variable was already declared and we
unknowingly shadowed it? And how would the compiler know to which variable to
assign the last return value?

This could be solved by allowing only one variable in the `else` condition.
Then the example above would be invalid, which it should be, because the if
statement would be more appropriate. Also if there was only one variable allowed
in the condition, there would be no confusion as to where the variable came
from.



I believe something like else catch could be beneficial to Go, mainly improving
readability. But there are a few problems to consider.

`else` would have different semantics depending on the context. This should not
be a problem for people, but it could be for the compiler and other tools.

Go would have a new, not widely known construct. How much would it impact the
learning curve of the language?



This is just an idea and I am interested in what you think.

   - Would else catch be useful to you?
   - Could it be simpler? Maybe a keyword or a built-in that could take the 
last return value and we could use if with a short statement instead of `else`.
   - Do you see a problem with it? Does it feel too much like magic?
   - Is it trying to solve a problem you even have?
   - Do the pros outweigh the cons?

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/06c41b88-5520-45b2-aad8-e3e74a2b1a86%40googlegroups.com.

Reply via email to