Comparing Go with Lua
steved-imaginaryreal.blogspot.com
steved-imaginaryreal.blogspot.com
=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#assertionsContrast 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.
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.
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.
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.
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.
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.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.
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...
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.