5 ms·
I think the "return an error code" approach is just as unreadable as nested or multiple try/catch blocks when the number of failure states grows large. I think
by cromwellian 14y ago
I think the "return an error code" approach is just as unreadable as nested or multiple try/catch blocks when the number of failure states grows large. I think the "Maybe Monad" approach of functional languages and Scala, combined with pattern matching in the language tends to look cleaner, e.g.
some_expression match {
case Some(x) => do_something_with(x)
case None => no result (e.g. null in C/Java/etc)
case Error(x) => handle error x
}
One of the nice things about the monad approach is they can be combined together. Consider the following chain of function calls which may return null at any point:
int val = foo().bar().baz().blah();
To deal with this with if/else statements, you need 3 nested if-statement for a single line of expression. In a language like Scala, you can just write:
for (a <- foo();
b <- a.bar();
c <- b.baz()) c.blah();
I think there is an inherent tension between explicitly detailing everything and boilerplate in terms of readability and writeability. I haven't seen enough Go code or written much of anything to say anything about it, except that it worries me. I do think the multi-return stuff can limit the amount of accident ignoring of error conditions, but the Go language itself doesn't provide any high level syntactic constructs to make dealing with the errors less painful, whereas with the Monad approach (which essentially is multi-return), the pattern matching tends to look cleaner IMHO.
- irahul 14y ago> "Maybe Monad" approach of functional languages and Scala, combined with pattern matching in the language tends to look cleaner, e.g. It looks the same to me. case maybeValue of Just value -> ... Nothing -> ... isn't any different from: if (val != null) { .... } else { ... } http://news.ycombinator.com/item?id=4721748 http://news.ycombinator.com/item?id=4721748 > Consider the following chain of function calls which may return null at any point: int val = foo().bar().baz().blah(); Like val = foo.bar.blah.boo rescue nil Or try { val = foo.bar().blah().boo() } catch(NullPointerException ex) { } Or try: val = foo.bar().blah().boo() except AttributeError: pass http://news.ycombinator.com/item?id=4726266 http://news.ycombinator.com/item?id=4726266 Personally, I do see Maybe/Option as cleaner, but it isn't novel as people make it out to be.
- yummyfajitas 14y agoIt looks the same to me. The key point is that because Maybe is a monad, you can chain things together and handle failure at the end much like a try/catch: let result = do x <- f(t) y <- g(x) z <- h(y) z in case result of ... You are correct that this isn't particularly novel.
- irahul 14y agoAnd as Go lacks both exceptions and Maybe/Option type, chaining or computation expressions will look more clumsy. I am still a Go beginner and am wondering if idiomatic Go designs programs in a different way to make up for this.
- pcwalton 14y agoThe first two examples you gave are very different. The compiler ensures that you don't forget to check the null case when using the Maybe monad, while you can forget to handle the null value and get a NullPointerException later. Also, the semantics of catching a NullPointerException in a chain of method calls are different from chaining Maybe results together. If any of the method executions inside raise an unhandled NPE, you'll catch it outside, which is probably not what you wanted to do.
- irahul 14y agoAgreed on both points. I was mostly pointing out that Maybe functionality will need some work from developer side, but can be easily done if you want it that way. > The compiler ensures that you don't forget to check the null case when using the Maybe monad, while you can forget to handle the null value and get a NullPointerException later. Though I use mostly dynamic languages with "everything an object"(or close approximation) where everything is effectively a Maybe, I do see the value in enforcing checks before using Maybe. In practice, I try to minimize the nullable types, as it is (too easy to get lazy | not know about implementation) and forget the check. Sometimes I use exceptions for flow control rather than returning null. > If any of the method executions inside raise an unhandled NPE, you'll catch it outside, which is probably not what you wanted to do. Yes. That will be bad. In an ideal world, given foo().bar().baz(), there shouldn't be a NullPointerException inside foo/bar/baz given how I am using them. If there can be one, I shouldn't be chaining them that way. But it's hardly an ideal world.