Live data from Hacker News

Another go at the Next Big Language

dave.cheney.net

11–20 of 125 posts

Re: Another go at the Next Big Language

#11
post #10

My kingdom for someone who can figure out how to solve error handling. My code consists of some reasonably straightforward sequence of actions with a random smattering of error handling significantly distracting from that. That error handling code is tedious to write, very time consuming to test (and often virtually impossible) and usually not run very often. Exceptions at least let you put the handling code somewher…

You and I sound like we may be cut from similar cloth. I find Go's approach to error handling -- which is to not to do much about the problem at all -- is better than pervasive use of exceptions but worse than some other possible mechanisms (and on this we may disagree). Although I'm not saying the language Rust has got it right, perhaps some of the thoughts on the mailing list may be useful. However scarily, my mail…

We're interested in adding support for condition handlers. Arbitrary stack unwinding and/or first-class continuations aren't likely due to runtime/toolchain limitations, but the "register a function to be called on error, which might direct the call to be restarted" style is intended to be supported.

Re: Another go at the Next Big Language

#12
post #10

My kingdom for someone who can figure out how to solve error handling. My code consists of some reasonably straightforward sequence of actions with a random smattering of error handling significantly distracting from that. That error handling code is tedious to write, very time consuming to test (and often virtually impossible) and usually not run very often. Exceptions at least let you put the handling code somewher…

You and I sound like we may be cut from similar cloth. I find Go's approach to error handling -- which is to not to do much about the problem at all -- is better than pervasive use of exceptions but worse than some other possible mechanisms (and on this we may disagree). Although I'm not saying the language Rust has got it right, perhaps some of the thoughts on the mailing list may be useful. However scarily, my mail…

I actually prefer exceptions to Go's approach, mainly because most of my code is structured with the best place to handle errors being higher up the call chain. Exceptions let me do that for free, whereas Go requires boilerplate code all over the call chain.

BTW your posting left out that close() can also fail (eg deferred writes run out of disk space).

And note that none of these really help with testing. In your 'cat' example, I'd want to test open failing, read failing, write failing and close failing. At a certain level the combination of language and libraries know these can fail. Ideally I would like to write zero lines of code to exercise those cases. In practise I usually find out that my test code is larger than the code being tested, and I usually have to add all sorts of instrumentation to force various failure modes which again makes my original code even longer and more obfuscated.

Re: Another go at the Next Big Language

#13

My kingdom for someone who can figure out how to solve error handling. My code consists of some reasonably straightforward sequence of actions with a random smattering of error handling significantly distracting from that. That error handling code is tedious to write, very time consuming to test (and often virtually impossible) and usually not run very often. Exceptions at least let you put the handling code somewher…

My kingdom for someone who can figure out how to solve error handling. You don’t really seem to want someone who can solve error handling. You seem to want someone who can make it disappear , or at least get as close as possible to that ideal. I feel your pain, but I fear you are asking the impossible. :-( Some errors are recoverable, and maybe it would be helpful to have default policies like “if the file is read on…

I agree that zero lines of error handling is the perfect solution. Even getting error handling and testing to be a trivial percent of the actual useful code would be a major improvement. At the moment my testing code is around the same size as the useful code plus error handling, and error handling is around 10% to 50% of the code.

I think part of the problem is error reporting is still somewhat shaky. To a certain extent you only actually need to handle errors that occur in the real world. Some languages like Java and Python can get you a reasonable amount of information when an exception occurs, but you still have to find mechanisms to report it back, and chances are you won't have detailed program state or operations leading up to the error. But if you did you could in theory release programs with no error handling as alpha releases (in the original sense, not current marketing usage) and see what shakes out.

> I don’t know what kind of testing you have in mind

Using fdr's example of a cat program, it consists of an open, a sequence of read followed by write and a close. I would like to point the language/environment at that and say "test it" with no further effort. Currently I have to write far more code to test, and then add in instrumentation to force various failures (eg make close return an error). The test code in this example would take considerably longer to develop and could itself have bugs!

Re: Another go at the Next Big Language

#14

My kingdom for someone who can figure out how to solve error handling. My code consists of some reasonably straightforward sequence of actions with a random smattering of error handling significantly distracting from that. That error handling code is tedious to write, very time consuming to test (and often virtually impossible) and usually not run very often. Exceptions at least let you put the handling code somewher…

Lisp had a condition system that was very clever. You still had to write the code, but you were able to separate the problem, the handling, and the restart.

This is the problem that comes up in many languages and which needs to be dealt with:

"Because each function is a black box, function boundaries are an excellent place to deal with errors. Each function--low, for example--has a job to do. Its direct caller--medium in this case--is counting on it to do its job. However, an error that prevents it from doing its job puts all its callers at risk: medium called low because it needs the work done that low does; if that work doesn't get done, medium is in trouble. But this means that medium's caller, high, is also in trouble--and so on up the call stack to the very top of the program. On the other hand, because each function is a black box, if any of the functions in the call stack can somehow do their job despite underlying errors, then none of the functions above it needs to know there was a problem--all those functions care about is that the function they called somehow did the work expected of it.

In most languages, errors are handled by returning from a failing function and giving the caller the choice of either recovering or failing itself. Some languages use the normal function return mechanism, while languages with exceptions return control by throwing or raising an exception. Exceptions are a vast improvement over using normal function returns, but both schemes suffer from a common flaw: while searching for a function that can recover, the stack unwinds, which means code that might recover has to do so without the context of what the lower-level code was trying to do when the error actually occurred.

Consider the hypothetical call chain of high, medium, low. If low fails and medium can't recover, the ball is in high's court. For high to handle the error, it must either do its job without any help from medium or somehow change things so calling medium will work and call it again. The first option is theoretically clean but implies a lot of extra code--a whole extra implementation of whatever it was medium was supposed to do. And the further the stack unwinds, the more work that needs to be redone. The second option--patching things up and retrying--is tricky; for high to be able to change the state of the world so a second call into medium won't end up causing an error in low, it'd need an unseemly knowledge of the inner workings of both medium and low, contrary to the notion that each function is a black box.

Common Lisp's error handling system gives you a way out of this conundrum by letting you separate the code that actually recovers from an error from the code that decides how to recover. Thus, you can put recovery code in low-level functions without committing to actually using any particular recovery strategy, leaving that decision to code in high-level functions.

To get a sense of how this works, let's suppose you're writing an application that reads some sort of textual log file, such as a Web server's log. Somewhere in your application you'll have a function to parse the individual log entries. Let's assume you'll write a function, parse-log-entry, that will be passed a string containing the text of a single log entry and that is supposed to return a log-entry object representing the entry. This function will be called from a function, parse-log-file, that reads a complete log file and returns a list of objects representing all the entries in the file.

To keep things simple, the parse-log-entry function will not be required to parse incorrectly formatted entries. It will, however, be able to detect when its input is malformed. But what should it do when it detects bad input? In C you'd return a special value to indicate there was a problem. In Java or Python you'd throw or raise an exception. In Common Lisp, you signal a condition.

A condition is an object whose class indicates the general nature of the condition and whose instance data carries information about the details of the particular circumstances that lead to the condition being signaled.3 In this hypothetical log analysis program, you might define a condition class, malformed-log-entry-error, that parse-log-entry will signal if it's given data it can't parse."

http://www.gigamonkeys.com/book/beyond-exception-handling-co...

Read the whole thing. It was a very clever system and wish something like it was available in other languages. (The lack of it in Clojure was one of the few things I agreed with Loper about in his anti-Clojure rant: http://www.loper-os.org/?p=42)

Re: Another go at the Next Big Language

#16
It's a pity that we are still talking about things like syntax in the context of a next big language. Programs are still full of bugs, especially concurrent programs, lots of time and effort is still spent on testing. At this day and age, the NBL should be a language that helps or guides a programmer write correct concurrent programs with good performance. It should prevent programmers from making mistakes as much as possible. I think the NBL will be in the same school as Erlang and Scala.

Re: Another go at the Next Big Language

#17
post #10

Earlier quoted context omitted.

You and I sound like we may be cut from similar cloth. I find Go's approach to error handling -- which is to not to do much about the problem at all -- is better than pervasive use of exceptions but worse than some other possible mechanisms (and on this we may disagree). Although I'm not saying the language Rust has got it right, perhaps some of the thoughts on the mailing list may be useful. However scarily, my mail…

I actually prefer exceptions to Go's approach, mainly because most of my code is structured with the best place to handle errors being higher up the call chain. Exceptions let me do that for free, whereas Go requires boilerplate code all over the call chain. BTW your posting left out that close() can also fail (eg deferred writes run out of disk space). And note that none of these really help with testing. In your 'c…

Go's panic/recover pattern lets you handle exceptions in the place you feel is best, much like exceptions. Though it is true that you would still need to check the error returned, and panic if not nil.

However, there are a few libraries that offer 2 versions of the same functions, one that returns the value and error combo, and one that panics in case of error and returns only the value (regexp' Compile and MustCompile for example). When it makes sense, it's nice to have this option.

Anyway, error handling is hard, I'm not a fan of exceptions, though alternatives such as Node's error as first argument of a callback and Go's second return value require polluting the normal code path. I like go's "defer" idea as a "finally", though.

Re: Another go at the Next Big Language

#18

Earlier quoted context omitted.

My kingdom for someone who can figure out how to solve error handling. You don’t really seem to want someone who can solve error handling. You seem to want someone who can make it disappear , or at least get as close as possible to that ideal. I feel your pain, but I fear you are asking the impossible. :-( Some errors are recoverable, and maybe it would be helpful to have default policies like “if the file is read on…

I agree that zero lines of error handling is the perfect solution. Even getting error handling and testing to be a trivial percent of the actual useful code would be a major improvement. At the moment my testing code is around the same size as the useful code plus error handling, and error handling is around 10% to 50% of the code. I think part of the problem is error reporting is still somewhat shaky. To a certain e…

Using fdr's example of a cat program, it consists of an open, a sequence of read followed by write and a close. I would like to point the language/environment at that and say "test it" with no further effort.

OK, so I suppose the next question becomes what “test it” means.

Perhaps you want your test system to make some kinds of automatic deductions about the intended behaviour of your code and then to verify that the actual behaviour matches? If that is the case, what kinds of deductions would you want to be handled automatically for you?

Or maybe you’re thinking of some sort of automatic simulation of possible failure cases when you get to the open/read/write/close operations, serving a similar roles to things like mocks and stubs in unit testing?

Some of these are the kinds of problems I’m hoping a good effect system will help with. If you know that the operations of opening, reading, writing and closing files must occur only in certain orders, you can automatically detect violations of those rules. And if you know all possible types of cause and effect that can be relevant within each part of your code — that is, you can identify all stateful/impure/externally observable results — then you can identify any of them that aren’t handled according to some set of rules. As long as you can figure out what you want those rules to be, that is...

Re: Another go at the Next Big Language

#19
I have to agree with the commenters on that article that JavaScript is the next big language. With HTML5 it's pretty amazing what you can do with JS. It's reached the point of being nearly as powerful as any thick client technology yet with ubiquitous browser and OS support.

It performs fairly well too: http://shootout.alioth.debian.org/u32/javascript.php . I'm not sure why one test is 100x slower, but the rest are < 10x slower than Java.

Re: Another go at the Next Big Language

#20

My kingdom for someone who can figure out how to solve error handling. My code consists of some reasonably straightforward sequence of actions with a random smattering of error handling significantly distracting from that. That error handling code is tedious to write, very time consuming to test (and often virtually impossible) and usually not run very often. Exceptions at least let you put the handling code somewher…

Lisp had a condition system that was very clever. You still had to write the code, but you were able to separate the problem, the handling, and the restart. This is the problem that comes up in many languages and which needs to be dealt with: "Because each function is a black box, function boundaries are an excellent place to deal with errors. Each function--low, for example--has a job to do. Its direct caller--mediu…

Consider the hypothetical call chain of high, medium, low. If low fails and medium can't recover, the ball is in high's court. For high to handle the error, it must either do its job without any help from medium or somehow change things so calling medium will work and call it again. [...] The second option--patching things up and retrying--is tricky; for high to be able to change the state of the world so a second call into medium won't end up causing an error in low, it'd need an unseemly knowledge of the inner workings of both medium and low, contrary to the notion that each function is a black box.

But if there was some way for medium to recover by itself, it had the option to handle the exception on the way back up the call stack and then to restart the computation that called low, hopefully under improved conditions.

Control will therefore return to high only if medium doesn’t itself know how to handle the error. At that point, the entire computation for which medium was responsible has failed.

Assuming the system as a whole uses reasonable functional decomposition and modular design, since exceptions have limited value under other circumstances anyway, shouldn’t any context that was known only within medium be irrelevant to any recovery action taken at high’s level?

Post reply on HN