Hacker News new | past | comments | ask | show | jobs | submit login
Comparing Go with Lua (steved-imaginaryreal.blogspot.com)
64 points by mapleoin on Sept 9, 2011 | hide | past | favorite | 14 comments



I'm a big fan of Lua and Go's use of multiple return values, nil, and simplified error handling, (No exception 'types'). I think the problem with 'typing' Exceptions is that it means you are using it to record and pass describable\known state similar to return, instead of being reserved for unknown state. For indicating predictable failure states without using exceptions, I think multiple return values as used in Go and Lua is a much better paradigm. To recap Steve's post, opening a non-existent file in Lua:

    =io.open 'sdfasdf'
    nil	sdfasdf: No such file or directory	2
The first value returned is nil, the 2nd value is a string containing an error message, and the 3rd is an integer error code. This allows you to write most code using 2 state boolean logic:

    f=io.open 'sasdasdfsf'
    if f then -- nil is a boolean 0
whereas in Python, one often has to reason about 3 states due to the use of try\except blocks. Even though you are not using typed Exceptions, multiple return values does not discard any state about the error if it is desired:

    f, err = io.open 'sasdasd'
    print(err)
In Lua (not Go), when you combine multiple return values with an assert() function of the form assert(boolean, error_message), you effectively get a mechanism for making exceptions optional:

    > =assert(io.open 'sdfasdf')
    stdin:1: sdfasdf: No such file or directory
    stack traceback:
    	[C]: in function 'assert'
    	stdin:1: in main chunk
    	[C]: ?
Although Go does not appear to include assertions at all, arguing against them in its FAQ: http://golang.org/doc/go_faq.html#assertions


The idea of exceptions is not primarily reading out details from the throw exception object, but to let programmers code the "happy flow" as if errors can not happen. It also makes it easier to let callers further up in the call-chain to set the policy of of what to do in case of an error.


And then, if you go one step further to conditions (Smalltalk, Common Lisp) callers can even setup a recovery policy for the error without unwinding the stack.

Smalltalk systems use this to great success to allow for e.g. dropping straight into the debugger at an error point (without any loss of context) even when the system is not technically being debugged.


I had Common Lisp Conditions in mind when I wrote my message above. Have an upvote for knowing stuff. :)


> The idea of exceptions is not primarily reading out details from the throw exception object, but to let programmers code the "happy flow" as if errors can not happen.

The problem is that errors can and will happen, and exceptions make the control flow of error handle unpredictably convoluted.

From a control flow point of view exceptions are worse than COMEFROM.

> It also makes it easier to let callers further up in the call-chain to set the policy of of what to do in case of an error.

Callers that do not have any of the real context in which the error happened, and that often can't do anything particularly smart about it other than throw their hands in the air.


Thanks for mentioning

http://en.wikipedia.org/wiki/COMEFROM

A joke in 1973 -- kind of a reality with the exceptions. As early as FORTRAN IV, IO operations errors were handled in higher level programming languages:

  WRITE( 6, 30, ERR=390 ) 
specified that error handling for that given write operation was behind the label 390.

The march of progress, just like even the first FORTRAN circa 1956 did

     WRITE (6, 150) X
 150 FORMAT (F10.2)
But C++ since 1988 improved it with

  cout << setw(10) << setprecision(2) << showpoint << x;
or Java 1996 with:

    java.text.NumberFormat formatter = 
         java.text.NumberFormat.getNumberInstance(); 
    formatter.setMinimumFractionDigits(2); 
    formatter.setMaximumFractionDigits(2); 
    String s = formatter.format(x); 
    for (int i = s.length(); i < 10; i++) 
         System.out.print(' '); 
    System.out.print(s);
The march of progress.


> The problem is that errors can and will happen, and exceptions make the control flow of error handle unpredictably convoluted.

I take it you never have seen the "leaning Eiffel tower of nested if's in C/Pro-C? As I wrote, in a language with exception handling you code as if errors never would happen. And then you put using/try+finally/block-exit clauses around your statements to assure cleanup will happen no matter what.

How is that convoluted? Good such code looks almost naïve to me.

>Callers that do not have any of the real context in which the error happened, and that often can't do anything particularly smart about it other than throw their hands in the air.

Well, exactly how smart a caller can be is decided by the protocol between caller and callee. It's no different than what return-values would have given you IMHO.


IMO going back to return statements for error detection is to throw away years of advancement. The problem with what you're describing is now every call has to be tested for errors because only the call-site has a chance to do this.

Contrast this with exceptions: if I don't know what to do with a failure at this level then I just ignore it. Some caller higher up the chain will know and can handle it.

Your examples seem overly trivial. In real code it would be very rare for the level of code that's actually opening files (something far down in the libraries) to also be handling exceptions.


Go actually does provide a mechanism similar to exceptions. The functionality is provided by the functions panic, defer, and recover. Idiomatic Go does not like panics (i.e. exceptions) to escape API boundaries, but otherwise you are just as free to use them as any language providing exceptions.


Years of advancement or years of wandering aimlessly? It depends on the coders, of course. More often than not I've seen exceptions used for non-exceptional errors: normal errors best handled immediately by the code that triggered it. The use of exceptions there needlessly defers dealing with the error, passing it up the call chain to code that shouldn't know particulars.

Making both returned errors and exceptions available at least offers the opportunity to use the better tool for the circumstances. Both are very valuable and clear under different circumstances.


I independently ended up with the same impression with the OP. Go feels just like a statically typed Lua. Not only does Go inherits Lua in some specific language features as pointed out by the OP, but it also learns from Lua's design philosophy: be minimal.

When I was going through my journey with Go discovery, I noticed that the Go authors obsessively tried to keep the syntax size small. There is only one looping construct. The only polymorphism mechanism is interface, but interfaces are both very simple and flexible. This is like what Lua does with tables.

This also means that when a feature introduces complexity into the language, the Go authors choose to discard it. There are advantages and disadvantages with exceptions, but I think what ultimately motivated the Go authors to leave exceptions behind is that it may complicate things. And Go also doesn't have any kind of compile-time generic, so one can't write functions like append() and copy() in pure Go.


> But the big similarity is this: functions can return more than one value. This is quite distinct from returning tuples, as Python does and is very much more efficient. (Bruce Eckel errs in speaking of tuples in Go) So the following style is common: a function returning a value plus an optional error:

I have trouble with this assertion: as far as I can tell, nothing would stop a language (especially a statically typed one) from optimizing a tuple return/unpack into a cheaper parallel assignment, and still provide a full-blown tuple type. I might be wrong, but is tuples unpacking not a strict superset of Go/Lua's restricted multiple return values?

(this is not possible in Python, because Python really unpacks arbitrary iterables, and Python 3 introduces ruby-style unpacking slices, so you can't apply such an optimization unless you can infer the exact return type of the callee)

Multiple return values is not intrinsically more efficient than tuples unpacking, it's just foisting an optimization onto the language user (by arbitrarily limiting his options in this case).

Even the following pattern of unpacking into a function arguments can trivially be handled via tuples unpacking (by making them explicit, which counts as a positive as far as I'm concerned)

> Go and Lua provide mechanisms for protected calls which are similar in spirit. Something bad will happen in a function, and the idea is to prevent that badness from bringing down the process.

a.k.a. exception handlers by any other name.

> The strength of the Go approach is that there is an explicit mechanism for calling arbitrary code when a function stack frame goes out of scope. With Lua (as with most garbage-collected languages) the best you can do is define finalizers on objects and wait for the garbage collector to call them (eventually.)

Right, because there's no such thing as `BlockClosure#ensure:`, `unwind-protect`, `using()` or `with:`.

> The difference is that with Go you have to use an underscore if you want to ignore the value (it is very strict about unused variables)

The issue with a bare `_` is that it has limited readability. Sometimes, you want to say "This is that, but I don't care for it". Erlang lets you do that by `_` being used as a prefix as well as a wildcard (technically I believe it introduces bindings, but more interestingly it makes the compiler not warn about unused bindings). It is very useful for bit positional matches of which half are thrown out.

> Note also the similar for-iterator syntax which naturally flows out of multiple return values.

Which you can just as easily get from tuples unpacking, of course.

> Lua's multiple returns came from CLU according to The Evolution of Lua. It is interesting how long it can take for new programming language concepts to reach the mainstream.

About 17 years[0] is not that much, and that's only if you discount pattern-matched functional languages[1].

[0] http://docs.python.org/reference/simple_stmts.html#assignmen...

[1] http://en.wikipedia.org/wiki/ML_(programming_language)


"I have trouble with this assertion: as far as I can tell, nothing would stop a language (especially a statically typed one) from optimizing a tuple return/unpack into a cheaper parallel assignment, and still provide a full-blown tuple type. I might be wrong, but is tuples unpacking not a strict superset of Go/Lua's restricted multiple return values?"

Well, it depends on how you implement polymorphic code, but generally yes. This is why we use tuples instead of multiple return values in Rust. In fact, if you return a struct by value in LLVM, LLVM will allocate a virtual register for each element.


It would be nice if the code samples were colored or tagged according to language, since I know neither Go nor Lua.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: