Live data from Hacker News

Why I’m not leaving Python for Go

uberpython.wordpress.com

191–200 of 245 posts

Re: Why I’m not leaving Python for Go

#191

Earlier quoted context omitted.

Problem is that you then have to explain monads to average programmers who'll be using the language. I love monads (I love arrows more, but that's another issue). I'm a language design geek. My level of expertise is different from someone who has just been hired into a new job and wants to get things done. I suspect that most language designers know about monads - I'm not sure Rob Pike did, but it's pretty common kno…

>Problem is that you then have to explain monads to average programmers who'll be using the language. No you don't, this is exactly what you don't need to do. Do we explain the finer points of stream implementation to would-be C++ programmers? No, when they need to do something non-standard with a stream/monad, that's the time to talk about them. For the majority of programmers it's just "when you need to write to a…

The Either type described in the first comment is also a Monad. They are more important in Haskell than just "when you need to write to a file..." and I don't think you can totally grasp the error handling method without understanding them.

Re: Why I’m not leaving Python for Go

#192
post #29

I'm not in love with this aspect of Go either, and I also find that the idiom for dealing with it (multiple return values and multi-statement if conditional clauses) doesn't play well with Go's scoping rules, so that I find myself regularly having to decide between cleaner conditional or an explicit variable declaration. I also don't love how it makes my code look like my teenage-years C code. But I also think this i…

Some time ago, I left COBOL and RPG-III, thanks.

Re: Why I’m not leaving Python for Go

#193
post #80

As usual in these threads about Go, I really wish people would consider Haskell as a nice alternative. A lot of people write Haskell off as "academic" or "impractical", which I feel is not an entirely fair assessment. Particularly: Haskell is fast, concurrent by design (whatever that means, I'm sure Haskell is :P), typed but not cumbersome or ugly (less cumbersome and ugly than Go's types, even) and--most importantly…

"So you can actually just write code like this: do val1 I have seen this (the error monad) mentioned before as a "nice" way of handling errors, even with explicit error returns. I beg to differ - the error messages produced by such a program will be obscure, as all the context is lost - if the first call to someFunction fails, for example, there's not necessarily any indication that the error came from that call rather than the one after it.

Go's explicit error handling means that it's easy to add meaningful context wherever relevant - the error messages printed by such a program are likely to be considerably more useful.

In the end, adding error checks is doing useful work. Each error case should be considered individually, and I've often found it to be the case that it's useful to treat errors as regular values (for example by collecting a bunch of errors, or returning the most important error only).

I can understand the control flow of a Go function by inspecting it on the page (without a glance at the documentation). That's a huge plus.

Re: Why I’m not leaving Python for Go

#194
post #179

Earlier quoted context omitted.

> One problem with Go is that is uses multiple return values to indicate errors instead of alternative return values. When you call strconv.ParseFloat, you always get back two values, the error code and... wait, what float do you get back when there's an error? Why would you even be interested in the float when there's an error?

That's his point: why do you get something typed as float back when there's an error? And what does that thing mean?

But if there was an error, WHY WOULD YOU EVEN BOTHER?! Read the documentation what it says about the value in case of an error, and stop inflating a non-issue.

Re: Why I’m not leaving Python for Go

#195

Earlier quoted context omitted.

>Problem is that you then have to explain monads to average programmers who'll be using the language. No you don't, this is exactly what you don't need to do. Do we explain the finer points of stream implementation to would-be C++ programmers? No, when they need to do something non-standard with a stream/monad, that's the time to talk about them. For the majority of programmers it's just "when you need to write to a…

The Either type described in the first comment is also a Monad. They are more important in Haskell than just "when you need to write to a file..." and I don't think you can totally grasp the error handling method without understanding them.

I couldn't disagree more. The Either type may have a Monad interface, but it's a Sum type and languages that don't have thousands of Monad tutorials also have Sum types. There's no need to explain Monad theory to someone just to explain Sum types or even why chaining do expressions doesn't do unnecessary computations in the face of errors. Just show them the code for join and >> and they'll see why it works. No need to bring up Monads.

Re: Why I’m not leaving Python for Go

#196
post #149

Earlier quoted context omitted.

The python mechanism is more general, you can use the /with/ construct with locks for example.

You can do the same with Java. Both are in a sense trying to re-gain the most useful parts of RAII semantics from C++: making it much more difficult to forget to clean up resources after you've finished with them.

Or the unwind-protect faciliy from Common Lisp, that is "cleanup stuff before it goes out of scope"

Re: Why I’m not leaving Python for Go

#197
post #156

Earlier quoted context omitted.

Yes. The advantage of the second form is that x := something() n := somethingElse(x) andSoOn() no longer compiles. Obviously you would also want some syntactic sugar to make it look nice, but that's quite simple.

There's also the option of doing it on an explicitly tagged union, Erlang-style: `something` returns not `Value` or `Either(Error, Value)` but `{ok, Value} | {error, Reason}`. This means you can handle the error: case something() of {ok, Value} -> %%; {error, Reason} -> %% end or you can "ignore" it {ok, Value} = something() but (and this is important) the latter *will not pass silently if `something()` returns an er…

That's just a slightly different syntax for Either as far as I can see.

Re: Why I’m not leaving Python for Go

#198
post #194

Earlier quoted context omitted.

That's his point: why do you get something typed as float back when there's an error? And what does that thing mean?

But if there was an error, WHY WOULD YOU EVEN BOTHER?! Read the documentation what it says about the value in case of an error, and stop inflating a non-issue.

Human beings are capable of making mistakes, that's the entire point. If you forgot to check the error code in go, then you will end up using that non-float float that shouldn't exist as if it were really a float. In a decent language, you get either an error or a float, you have no way to accidently use the float if an error occurred.

Re: Why I’m not leaving Python for Go

#199
post #188

Earlier quoted context omitted.

The two mechanisms are not very similar. With the blocks mechanism (if I understand correctly) the resource itself implements a method "execute this code and clean up yourself". This only uses the standard mechanisms of the language (i.e. blocks.) The control is with the resource. Python's 'with' inverts the control - the control is with the language runtime and the resource is passive here. In fact 'with' is a speci…

> With the blocks mechanism (if I understand correctly) the resource itself implements a method "execute this code and clean up yourself". Where the method is doesn't matter, e.g. the (incomplete) example I gave of implementing "with" in Ruby that shows a method that can be defined in the global "main" scope or wherever you want. Some classes implement class-methods to instantiate an object and pass it to a black and…

Which is why I gave my example of how I thought "with" would look like in Ruby (as it turns out I missed some exception handling in order for it to be equivalent to the Python version in functionality).

What you can't do, though, is express ruby's blocks using python's with statement; ruby is strictly more powerful here. One simple example of this is ruby's fork statement, which runs the code in the block in a process of its own:

    fork { puts "child" }
    puts "parent"
That will print "child" and "parent" once each, and it works because the fork method has control over the block's execution, so it can choose to only evaluate the block in the child's context. In python, the approximate code would be:

    with fork():
      print 'child'
    print 'parent'
However, python doesn't allow the resource to control block execution, so this code can't work (within standard python; there's a bytecode hack that can make it work, but that's outside the spec). Anyhow, I guess we've diverged pretty hard from the article, but I can never pass up the opportunity to vent against python's with statement.

Re: Why I’m not leaving Python for Go

#200
post #108

Earlier quoted context omitted.

I am glad to see some CL condition system pop up here. This article basically turned me off from Go and reading peoples support for C style error handling has made me question what the heck they are thinking. There are actually several nice things about the CL system, but nothing there is going to convince someone that actually thinks not only that having every return value in your language serve the role of an error…

Instead of insulting people and declaring that you can't convince them, try posting some useful information about CL condition system and why you think it's better than everything else.

First let me apologize for the length, second I will say that to my knowledge none of this is about the CL condition system in particular, third this is basically a brain dump because I need to get back to work and still don't understand the error code camps points...

I definitely wasn't trying to insult but I recognize that I wasn't being helpful either. I was basically throwing up my hands. And while I like CL's system, I haven't come close to trying everything out there so I can't be a judge; I mean this not like a cop out but seriously, I am very poorly informed about what other languages have to offer.

But since you ask: I guess if I was trying to convince someone that exceptions are inherently a better mechanism, I would start by exploring how arithmetic is handled in the C language. Let's say we have a function called "add" that adds two numbers. Typically we would think expect that the return value of such a function should hold the result of that addition. It would be naive, however, to expect add to always complete without something like an error happening. While addition of two numbers seems safe, it isn't when we start using our imperfect number representations such as floating point numbers (susceptible to overflow, underflow, loss of precision, and more) but also with machine sized integers (susceptible to overflow) and even bigints (what happens if we attempt to add two numbers and exhaust system heap space?). The way these are currently handled in the C language (to the best of my knowledge) is by three different mechanisms: 1) floats return special floats that say indicate that there was an error somewhere, like NaN (though you can probably instruct your OS/hardware/whatever to issue these as signals instead of silently spitting out an NaN), 2) wrap-around is loosely taken as a standard but truly it has undefined consequences, your code must guarantee that this cannot happen, and 3) the program will probably exit. This is messy but we have learned to deal with it and has become second nature to C programmers; but that doesn't means it is good.

But this doesn't need to be the case, we could use the return value checking that is being promulgated by some here. We can define add as...

error_code add(int a, int b, int *return)

...where int could be any number type and translate every occurrence of "a+b" in our code into something like...

int ret; if (err = add(a,b,&ret)) { // err handling here } // ret holds the answer here

I cannot believe this is preferable to anybody, in fact I am going to go out on a limb and say that it isn't. Instead we just trust this to work when we know that there are instances where it won't and, in the case of floating point math, it is extremely common for you to hit those cases where it doesn't work. If we were serious about writing code that handled these errors gracefully, our C code would be unmaintainable. If we were honest about the source of this laziness, I think we would say that this is due to the syntactic and mental overhead of the error handling. The fact that we don't code like this, even the very fact that C will spit out an NaN and propagate it along indefinitely, is proof in point that people dislike this type of "check the return value" error handling. This is C more or less pushing us to write code that is less robust.

If we really wanted to consider what happens in a Common Lisp system, you would have a function "add" that takes two numbers and will always return the correct result of that addition or won't return at all. This means no wrap around overflow, no NaNs; this is vastly cleaner. However, as far as I know, this has nothing to do with CL in particular, this is presumably how Python or any language with a exceptions works. This is what I cannot fathom: that there are people that are willing to throw away this simplifying assumption because it means that your code might have controlled non-local transfer of execution. This is what Joel on Software directly says (linked elsewhere in this thread); exception handling is like goto, goto is bad, even when it is contained within an apparatus fixes its problems, like the exception handling apparatus. Or that people think the same amount of work is involved in either system? How does that make sense? Which way would you rather treat something like "add(a, add(b, add(c, d)))" because to my eye the C style handling adds a bunch of boiler plate code that I'd rather not write while adding little to no value. Like I was saying, I cannot fathom it so it is hard to form an argument against the alternative stance that I do not understand. Perhaps there are places where we want our execution so close to the structure of the code that handling errors C style makes sense, but I have to imagine that they are few and far between.

If we compare a more common example where people will place error handlers: writing to a file. Is it better to have writefile(file, data) return an int that indicates error, or is it better to just say "writefile puts this data in file, period". Here are two errors that might come up when writing to a file, we don't have write permission for that file or the disk is full. Where do you want to handle these errors? One error happens when we open it, the other happens when we attempt to write to it further down the call tree, but presumably we might want to catch both and deal with them at the same location. With exceptions you catch/handle the exceptions you are interested in. When returning error codes you have to manually defer the error up the call tree, affecting the interface of every calling function up to the highest function that can actually handle the error. So you can add the boiler plate code to combine and propagate errors up the call tree to your task list now. At the root of this is the exact same issue as the previous point, it adds syntactic and mental overhead for the programmer.

This article seems to have spawned some responses. Consider this post: http://www.yosefk.com/blog/error-codes-vs-exceptions-critica... He makes claims like in the code...

open_the_gate() wait_for_our_men_to_come_in() close_the_gate()

...if there is an exception inside wait_for_our_men_to_come_in then we'll never close the gate. While this is true for the code he's written, most people would (or should) consider that code buggy. In my experience and the comments of that post confirm it, most languages have a mechanism to ensure certain side effects happen even under non-local control transfer. Where are the points of intermediate state that he render exceptions equally bad as error codes? Are they the states that we explicitly account for in any bug free program, like closing the gate if there is an problem bringing the men in? I will reiterate, I truly am at a loss, this is not a rhetorical question. It seems like I am missing something.

To sum up: One method seems like a clean method of dealing with errors that will happen, the other seems like a nightmare syntactically and basically consists of code that I deeply feel should be up to a compiler to write.

Post reply on HN