I will ignore all "this sux" and "exceptions are crap" and "checked
exceptions
are crap" type postings especially since the return type for errors is
the
equivalent to checked exceptions that are returned and not thrown.
Motivation
----------
1. Out of bounds index operations cause panic. Documentation says "If
the index
or key is out of range evaluating an otherwise legal index
expression, a
run-time exception occurs.". No way to recover from this.
2. The "printf" problem. Although printf in C can fail, very few, if
any, check
the return code. Return codes become cumbersome even for people
like myself
that are obsessive about error handling.
3. Exceptions were invented because return codes were found to be
unsatisfactory. Exceptions are good but existing implementations
are
problematic due to lack of determinism.
Requirements
------------
1. Exceptions must be easier to use than return codes and less
cumbersome.
2. The library user, or function caller, must never be in doubt as to
what
exceptions could be thrown (determinism).
Proposal
--------
Will add the following syntax:
catch [name] type|interface [,[name] type|interface ...] { handler
code }
catch throws type [,type ...]
catch fails type [,type ...]
endcatch type [,type ...]
throw [ExpressionList]
defer (failure) Expression
defer (success) Expression
The catch expressions work much like Visual Basic "on error ..." in
that the
catch expression is written before the code that could throw the
exceptions.
(See Example 1 below for illustration)
All public functions must declare the exceptions that can be thrown
using the
"catch throws" statement; any type except interface is valid. The
compiler
will determine what exceptions could be thrown and compare to the
throws list;
any not declared will cause the compiler to fail with no way to
override.
The developer cannot declare a "catch throws" in the main() function
or any
function that is invoked as a goroutine.
Private functions don't need to declare exceptions.
The "catch fails" statement is an assertion by the developer that an
exception
declared by a function or operation will never be thrown. For example,
when
using an index operation, (eg "x[i]") which could throw an
IndexOutOfBoundsException, the developer checks bounds and ensures
that
the operation will never be executed with an out of bounds index
value, the
developer can assert "catch fails *IndexOutOfBoundsException;" to
prevent the
compiler from failing on his public method. If, however, that
exception is
thrown, the program will exit with failure; similar to a panic except
that all
the calling defers are executed and the program is given a chance to
exit
cleanly.
A block creates a new scope for catch and only exceptions thrown
within that
block will be considered. Instead of using blocks to scope catches,
the
developer can use the "endcatch" statement to remove the handler for
subsequent
statements in the scope.
A catch block that would never be invoked will cause the compiler to
fail.
The "defer (failure)" statement will only execute when the current
scope is
exited due to exception thrown.
The "defer (success)" statement will only execute when the current
scope is
exited normally.
The "defer Expression" statement is unchanged, it will always execute
when the
current scope is exited.
There are a class of exceptions, indicated by the suffix "Error", that
can be
thrown by the runtime and are never declared by the developer. At this
time,
I can only think of one such example: "OutOfMemoryError".
Examples
--------
All the examples will make use of the following exceptions:
(I am not a fan of throwing primitive types but in the interest of
fighting only
one battle at a time, my example takes inspiration from "os.Errno")
type ProcessException int;
func (self ProcessException) String () {
<code to print a string>
}
var {
EMISSINGTRADES ProcessException = ProcessException
(errs.EMISSINGTRADES);
}
type OutOfMemoryError struct {
RequestedBytes int;
AvailableBytes int;
}
func (self *OutOfMemoryError) String () {
<code to print a string>
}
type IndexOutOfBoundsException struct {
InvalidIndex int;
UpperBound int;
}
func (self *IndexOutOfBoundsException) String () {
<code to print a string>
}
Example 1, A simple catch:
func main () {
catch e ProcessException {
log (e);
os.Exit (-1);
}
// Exceptions thrown here will be handled by the catch block above
functionThatCanThrowProcessException ();
os.Exit (0);
}
Example 2, Multiple catch blocks in a function:
func main () {
// catch block (A)
catch e ProcessException {
log (e);
os.Exit (-1);
}
// catch block (B)
catch e *IndexOutOfBoundsException {
// Do something with the exception
os.Exit (-1);
}
// Exceptions thrown here will be handled by the catch block above.
// *IndexOutOfBoundsExceptions thrown here will be handled by catch
block B.
functionThatCanThrowProcessException ();
// catch block (C)
catch e ProcessException {
if e == EMISSINGTRADES {
// do something here
}
log (e);
os.Exit (-1);
}
// ProcessExceptions thrown here will be handled by catch block C.
// *IndexOutOfBoundsExceptions thrown here will be handled by catch
block B.
anotherFunctionThatCanThrowProcessException ();
os.Exit (0);
}
Note, if a ProcessException were to be thrown in catch block C, it
would be
caught by catch block A. To prevent that from happening, the developer
can
introduce scope using a block as such:
func main () {
{
// catch block (A)
catch e ProcessException {}
// catch block (B)
catch e *IndexOutOfBoundsException {}
// ProcessException thrown here will be handled by the catch
block above.
// *IndexOutOfBoundsExceptions thrown here will be handled by
catch
// block B.
functionThatCanThrowProcessException ();
}
// catch block (C)
catch e ProcessException {
// If this handler threw a ProcessException, the compiler would
fail as
// the exception is not handled.
throw EMISSINGTRADES;
}
// ProcessException thrown here will be handled by catch block C.
// *IndexOutOfBoundsExceptions thrown here will cause the compiler
to fail
// as it cannot see catch block B in the nested scope.
anotherFunctionThatCanThrowProcessException ();
}
Alternatively, the developer can use the "endcatch" statement to
achieve the
same effect:
func main () {
// catch block (A)
catch e ProcessException {}
// catch block (B)
catch e *IndexOutOfBoundsException {}
// ProcessException thrown here will be handled by the catch block
above.
// *IndexOutOfBoundsExceptions thrown here will be handled by catch
block B.
functionThatCanThrowProcessException ();
endcatch ProcessException;
// catch block (C)
catch e ProcessException {
// If this handler would throw a ProcessException, the compiler
would
// fail as the exception is not handled.
throw EMISSINGTRADES; // <- compiler would fail here
}
// ProcessException thrown here will be handled by catch block C.
// *IndexOutOfBoundsExceptions thrown here will be handled by catch
block B.
anotherFunctionThatCanThrowProcessException ();
}
Alternatively, if the second handler code should never throw
ProcessException,
the developer can assert as such.
func main () {
// catch block (A)
catch e ProcessException {}
// Exceptions thrown here will be handled by the catch block above
functionThatCanThrowProcessException ();
catch fails ProcessException;
// catch block (C)
catch e ProcessException {
someHandlerFunctionThatShouldNeverThrowProcessExceptionButDeclaresIt (
alwaysValidParameter);
os.Exit (-1);
}
// Exceptions thrown here will be handled by catch block C.
anotherFunctionThatCanThrowProcessException ();
}
Example 3, suppressing an exception that the developer believes will
never
be thrown:
The following function will not compile as the compiler will complain
about
uncaught exception "*IndexOutOfBoundsException".
func main () {
i := 5;
a := [...]int {1,2,3,4,5,6};
x := a[i];
}
To allow the function to compile, add a "catch fails" assertion.
func main () {
// Asserts that the index operation will not throw this exception
catch fails *IndexOutOfBoundsException;
i := 5;
a := [...]int {1,2,3,4,5,6};
x := a[i];
}
Example 4, Handling multiple exception types with one block:
When an exception type was not caught, the value is the zero value.
func main () {
catch e1 ProcessException, e2 *IndexOutOfBoundsException {
if nil != e2 {
postBugReport ("Program error", e2);
} else {
log (e);
}
os.Exit (-1);
}
// Can throw either ProcessException or *IndexOutOfBoundsException.
DoProcessing ();
os.Exit (0);
}
Example 5, Declaring exceptions for public functions:
package util
func DoProcessing () {
catch throws ProcessException;
catch fails *IndexOutOfBoundsException;
.. some code here that throws ProcessException ...
.. some code here that uses indexers ...
}
package main
import "util";
func main () {
catch e util.ProcessException {}
util.DoProcessing ();
}
Example 6, Function that throws multiple exceptions:
package util
import (
"file";
"db";
)
func DoProcessing () {
catch throws ProcessException, file.FileNotFoundException
, file.FilenameTooLongException, file.AccessDeniedException;
catch fails *IndexOutOfBoundsException;
.. some code here that throws various exceptions ...
.. some code here that uses indexers ...
catch throws db.CorruptDatabase;
... some code that throws db.CorruptDatabase ...
}
package main
import (
"util";
"file";
"db";
)
func main () {
catch e util.ProcessException {...}
catch e file.FileNotFoundException {...}
catch e file.FilenameTooLongException {...}
catch e file.AccessDeniedException {...}
catch e db.CorruptDatabase {...}
util.DoProcessing ();
}
This can become cumbersome so instead, if all the exceptions were
handled in the
same way (eg log message and exit with error),the developer can catch
an
interface. Assuming all the exceptions implement an interface
"os.Exception"
which is the same as the existing "os.Error", then func main becomes:
func main () {
catch e os.Exception {
log (e);
os.Exit (-1);
}
util.DoProcessing ();
}
Just to reiterate, the syntax only allows for catching and handling of
exceptions by interface, it does not allow interfaces for "catch
throws",
"catch fails" and "endcatch".
Example 7, "On error resume next":
func main () {
catch os.Exception {} // <- empty catch block
util.DoProcessing ();
}
I do not think that built-in exceptions are a good idea. I did write
some about having yielding functions, but this proposal hasn't done
quite well, maybe something should be made more clear or simple there.
I'm sure that the overall idea of having function somewhat control the
caller's execution path would be good. Anyway - why only exceptions?
Why are those so special? Also, why to gather all that information
about code lines, callstacks etc. if they could be catched?
By the way, do you know the history of HTML? Not?
Phase 1: Errors accepted. Browser must fix everything.
Phase 2: Incompatible implementations, slow browsers, web messed up.
Phase 3: w3c standards actively promoted, people being proud to belong
the few, who write correct HTML
Phase 4: XHTML
Maybe the sequence is slightly different in some places, but this is
the overall schema. Maybe also there are other reasons for
incompatible browsers etc., but this "freedom to err" has still led to
a big confusion - and actually it's pretty useless to not write "</p>"
or occasionally write "<htlm>", which will be then "autocorrected"
silently.
Or the history of Windows?
Phase 1: A bit too complex implementation or smth.
Phase 3: A lot of error recovery built in - able to handle crashes of
different kinds, autorepair inconsistencies etc.
Phase 5: Windows getting slower and slower for no obvious reason,
doing some kind of expensive operations in background etc.
This is how it seems to me. This might be not overally correct, but I
clearly see windows growing larger and buggier in a while after
install, then starting to repair itself and do a lot of work to repair
numerous error conditions. No way to make it fast at that point.
So I did write this proposal:
functions can "yield" values. Yielded values are defined by some
interface-like structure. They can also _return_ values.
Think of that:
struct outofbounds interface {
outofbounds(i, min, max int) (choose int);
}
func (myarray A)get(int i) yields (outofbounds, ...) {}
And:
value := myarray.get(8) {
case outofbounds(i, min, max) (choose):
if (i < 0) {
// Removes 2 levels from callstack
errorpos, errorstring <- reflection.currentPos(2), "Value less
than 0";
panic("Unrecoverable error");
}
choose = max;
case nil() (ret):
ret = V{12}; // 12 is default value.
}
For now I'm playing with ideas to use a "chan { ... }", a channel
interface, for whole this topic instead. To keep the primitives few.
There is also "defer" statement, which can clean up for you. But
"panic", in many cases, is much better than error-correction - I can
understand that this should be handled if network went down or file
was locked, but I can't see why you should recover from boundary
panic? Just return some random element and go on?
I am not quite sure I understood all you said.
On Dec 19, 2:06 am, Qtvali <qtv...@gmail.com> wrote:
> So I did write this proposal:
> functions can "yield" values. Yielded values are defined by some
> interface-like structure. They can also _return_ values.
>
> Think of that:
> struct outofbounds interface {
> outofbounds(i, min, max int) (choose int);
>
> }
>
> func (myarray A)get(int i) yields (outofbounds, ...) {}
>
> And:
> value := myarray.get(8) {
> case outofbounds(i, min, max) (choose):
> if (i < 0) {
> // Removes 2 levels from callstack
> errorpos, errorstring <- reflection.currentPos(2), "Value less
> than 0";
> panic("Unrecoverable error");
> }
> choose = max;
> case nil() (ret):
> ret = V{12}; // 12 is default value.
>
> }
If I read your example correctly, the yielded value must be processed
in an block linked to the statement. Is that correct? If so, how would
you handle errors in an operation or multi-part expression? I see two
major problems with the suggestion. First, if I understand correctly
that the yielded value must be processed immediately, how is that any
different from returning an error value or structure and testing with
an "if" statement? Second, the suggested mechanism does not fix the
problem with operations (index lookup, divide by zero, integer
overflow, invalid number etc).
> "panic", in many cases, is much better than error-correction
I have to disagree. Once you have to create a server or application
that must run 24x7 99.999% of the time (the five 9's), you will see
that having your program suddenly quit with no chance to clean up and
release resources is a bad idea. We, as humans, are not perfect and
even if we try to cover every possible scenario in tests, sometimes
budget and time constraints mean we have to focus on the most
critical / fault sensitive areas of the application. Thus sometimes an
obscure and unforeseen condition manifests and it would be nice if the
program is able to recover, or even exit cleanly with an appropriate
error in the log as well as sending an alert to the administrators
that an unexpected failure occurred.
> was locked, but I can't see why you should recover from boundary
> panic? Just return some random element and go on?
No, but wouldn't it be nice to be able to react to the error, for
instance to log a message, or disconnect from the database, or release
a mutex, or free memory (if this was Windows) etc. The current
implementation of the GO runtime just collects the stack information
and exits the program. Not very useful.
Here is an example where handling an unexpected error raised as an
exception is useful.
Given a program that must process a text file of about 700 million
records to extract certain data and upload into a database. The text
file is formatted with records of fixed length and to a specification.
The program expects the input file to be formatted correctly and thus
does not perform many checks during processing (this program was
written before my time). The programmer did think ahead and included a
catch-all exception handler that, when an error is caught, would roll
back the current transaction and clean up any records uploaded during
the processing of the file. The program is set to clean up the
database so it can be rerun after the cause of the error has been
resolved. This program runs for years with no problems until one
night, after an upgrade of the source system (that generates the huge
file) a bug in that version writes out an invalid number (J000000)
instead of 000000. The original programmer did not expect that a
numeric field would be populated with non-numeric data but because of
exception handling, the NumberFormatException is caught, the database
is cleaned up and enough information was logged so that the night
operators could clean up the file and rerun the application. Thus the
next day at 7am, the traders had the information they needed on hand
instead of having to wait for the programmers to arrive and resolve
the problem.
As an aside, the analogue of panic is "catch fails" with the
difference that the defers and cleanup routines are allowed to
execute.
Regards,
- Myron
I still think that those should not be the standard exceptions. Your
case is not pretty much clear (I have done basically the same thing) -
rollback is done by SQL server automagically.
Anyway, that's true that server should not shut down after division by
zero. Anyway, this has been discussed here - exceptions as they are in
Java won't do that well. You basically describe Java exceptions with
almost Java syntax.
You don't think exceptions are useful. I do. We will have to just
agree to disagree.
Regards
- Myron
What we really need is some mechanism that allows the programmer to
handle unexpected failures in those situations where they feel
capable of doing something in response. For that, adding exceptions to
Go seems like overkill and overcomplication.
So, an alternate proposal:
The "go" keyword should return a chan interface{}.
When the goroutine terminates normally, a nil is written to the
channel.
When a fatal error occurs, a non-nil value is sent (perhaps a string
or an os.Error).
If the goroutine's termination channel is unused for some reason, the
value should get fed into
the channel of the goroutine that sparked it.
If it gets to the top level, it results in the process going down.
As an added benefit, this allows us to do:
func main() {
a := go DoSomething()
b := go DoSomething2()
// wait for running goroutines to finish before exiting.
<-a;
<-b;
}
Is there some fundamental flaw or inadequacy of this approach that I'm
missing?
> ...
>
> read more »
This only applies to goroutines. What about normal function calls? Or
just inline code blocks? Shouldn't any exception-style error-handling
system be applied more globally?
Correct me if I'm wrong, but it seems to be bad style (and
inefficient/wasteful) to spawn goroutines where concurrency isn't
required. What you describe encourages the use of goroutines where
they aren't at all needed.
Andrew
I think that there should be a keyword to construct this by yourself,
using reflection. For all cases, one would not need such thing - and
collecting this data about stack trace is seriously not anything a
language should do by default. But if reflection did turn on such
behavior and returned it, this would be fine - this, anyway, would
need more a specification about what this error collection should
contain (in Java, it contains a potentially large number of strings,
locations, information about calls etc., everything what is put
together after rotating an exception around for a while). I have also
thought that a routine-bound channel would have several usable
features; closing it when a routine ends is one of them. But I think
it takes a while before this error handling model is actually put
together, given the topics I've read here. And reasonably so - this
topic is quite large and current implementations have their drawbacks.
Anyway, if you look at the recent topics, then math routines, for
example, are going to have better error handling - don't know what
this means, but thus there is some effort behind that.
On Dec 19, 4:45 am, kyle <consa...@gmail.com> wrote:
> In my experience, the benefit of exceptions over return values is that
> they can handle unexpected fatal errors.
Before I answer, let me say that usage of exceptions does not preclude
error returns. You are welcome to design an API that does not include
them.
Also, this is the last time I am going to answer a post that debates
whether exceptions are the best thing for GO. I want exceptions. I am
convinced that exceptions are a requirement for complete error
handling in any modern language. This thread is to debate the form and
implementation of exceptions in GO, no whether to include them or not.
Also, if you want me to take you seriously, then provide comprehensive
and completely fleshed out ideas as I have.
The benefit of exceptions are that they can handle the case where a
return value is not possible.
For example:
Picture a program where there is a struct called Foo and a constructor
method called NewFoo.
Building Foo is a complex procedure that uses multiple resources, if
any of those resources are not available, then Foo cannot be
constructed so NewFoo will have to generate an error. Let us say that
in a function, multiple instances of Foo must be instantiated based on
various parameters. So you have the following scenario:
NewFoo (parm1)
NewFoo (parm2)
NewFoo (parm3)
In the current implementation of GO, NewFoo will have to return an
instance of Foo and an error with reason for failure:
func NewFoo (parm type) (*Foo, *ErrReason) {}
So the above translates to:
var err *ErrReason;
var foo1, foo2, foo3 *Foo;
foo1, err = NewFoo (parm1);
if nil != err {
.. do something with the error ..
.. maybe return false or something ..
}
foo2, err = NewFoo (parm2);
if nil != err {
.. do something with the error ..
.. maybe return false or something ..
}
foo3, err = NewFoo (parm3);
if nil != err {
.. do something with the error ..
.. maybe return false or something ..
}
What if I don't want to repeat the error handler? If I have a
situation where the first failure exits the function then I could
either do something like this:
foo1, err1 := NewFoo (parm1);
foo1, err2 := NewFoo (parm1);
foo1, err3 := NewFoo (parm1);
if nil != err1 || nil != err2 || nil != err3 {
var err *ErrReason;
if nil != err1 { err = err1 }
else if nil != err2 {err = err2 }
else if nil != err3 { err = err3 }
.. do something with err ..
return;
}
or I could do the following:
{
var err *ErrReason;
var foo1, foo2, foo3 *Foo;
foo1, err = NewFoo (parm1);
if nil != err {goto error}
foo2, err = NewFoo (parm2);
if nil != err {goto error}
foo3, err = NewFoo (parm3);
if nil != err {goto error}
return;
Error:
.. do something with err ..
return;
}
But what do I do if I want to try each statement and handle the error
for each one but put the code into a common area?
And more ...
Now lets say that you want to code using fluid interfaces. How would
you implement that in GO? A fluid interface is one that uses chained
method calls to create a type of domain specific language. Take the
example of a windowing system:
type Window struct {}
func (self *Window) SetSizeInPixels (x,y int) *Window {
...
return self;
}
func (self *Window) MoveToPixel (x,y int) *Window {
...
return self;
}
func (self *Window) SetTitle (title string) *Window {
...
return self;
}
func (self *Window) Draw () *Window {
...
return self;
}
func CreateWindow (windowType int) (*Window) {
...
window := new (Window);
...
return window;
}
To display a window, you could use the following:
window :=
CreateWindow (FRAME)
.SetSizeInPixels (100,100)
.MoveToPixel (320,200)
.SetTitle ("MyProgram")
.Draw ();
Where do you put there error return?
And more ...
How does return codes handle when the index "[]" operation is out of
bounds, division operator "/" errors (eg divide by zero), and other
math operation errors?
Current GO just panics and dies. Is this optimal?
or maybe every operation has a multiple return such:
[] => value, *SomeErrorStuct
/ => value, *SomeErrorStuct
% => value, *SomeErrorStuct
So if you wanted to do some calculation like:
result := x * 20 / y + 11 / z
would then have to be:
var result int;
var err *SomeErrorStruct;
result = x * 20;
result, err = result / y;
if err != nil {
.. fail here ..
}
result = result + 11;
result, err = result / z;
if err != nil {
.. fail here ..
}
Same for indexers:
val, err := x[y];
if nil != err {
.. fail here ..
}
Exceptions are a tool. Removing the tool from the box because previous
implementation were problematic is like throwing the baby out with the
bathwater.
Regards,
- Myron.
Thanks for the pointer.
Unfortunately, it is a messy hack to simulate try .. catch without the
real benefit of exceptions. On top of that, it does not solve the
operand error issues.
Regards,
- Myron.
if y != 0 {
....
} else {
}
is infintely better than exceptions in this case. same with many other
of your examples. Rather than do something then see if it failed, test
the input before doing it. This doesn't cover everything, but it
vastly reduces the cases where error endling would be used: don't
cause them in the first place.
Now, in the case of the stringing together of methods, for one thing
the code you showed is impossible in Go ( the compiler wouldn't check
the method call after whitespace) Another is that had you stored the
window in a variable, you would have pretty much the same code. I do
see the appeal in that though, especially if your doing several
operations in a row returning incompatible types where you don't care
about intermediate values, which becomes impossible with a multi
return. Well, you could do:
var inter interface{}
inter, err1 := method1()
inter, err2 := inter.(type1).method2()
...
And to handle the errors later, you could push them into a vector, or
(since you know how many) into a buffered channel.
I know this doesn't satisfy you fully, but, I think when figuring out
what problems actually need solving us a much better approach than
trying to redundantly solve a bunch of non-issues simply because they
were lumped together in some other implementation.
Also, can closures access labels? If so, you could define your routine
error check before hand, and go to the relevant error handling section
if necessary. Otherwise, you'd have to define your error handling
before the rest of your method body, and wouldn't be able to return,
or repeat if err != nil { goto error }, which isn't all that bad. It's
essentially what you't be asking the runtime to do.
I think the statements like
catch throws db.CorruptDatabase;
catch fails *IndexOutOfBoundsException;
endcatch ProcessException;
would be better as
catch db.CorruptDatabase { throw }
catch IndexOutOfBoundsException { fail }
catch ProcessException { end }
This is something I am completely convinced of based on my experience
in building 24x7 systems. I came to the conclusion that there were two
camps of thought for the "haters". The first camp are those who don't
care about error handling and are quite happy to have the program bomb
out on an uncaught exception. There are scenarios where this is valid
thinking but too often than not, it is applied to code that should be
handling errors properly. The second camp are those that consider
checking exceptions to be cumbersome and overkill. I have come to
agree with them on one, and only one, point; checking exceptions on
private methods is not necessary since they are not exposed to library
users.
The argument for checked exceptions is for determinism. In C++, there
is no declaration of exceptions thrown in the link library binary so
there is no way for the developer to know if a method could throw an
exception; the only time they find out is at runtime when the program
suddenly abends with a "dr watson" on Windows or core dump in Unix.
Trying to create 24x7 systems with this uncertainty is very difficult
and the reason that checked exceptions were introduced into Java.
Checked exceptions in Java did go some way to helping determinism in
that the developer was forced to declare them on every method so
tracing them back to source and knowing that an exception had to be
handled was an improvement. Unfortunately, they did not fix the
problem of determinism for two reasons: there are a class of
exceptions called "RuntimeExceptions" that are unchecked; and
developers can declare "throws Exception" which does state that an
exception must be handled but not what exception is to be handled
since all checked exceptions inherit from the "Exception" class.
I thought very hard about the reasoning behind the implementation and
how it is used by developers. I concluded that allowing inherited
classes to match the declaration (eg all exceptions matching "throws
Exception") was a compromise and a mistake. Thus I came up with the
following:
1. Declared checked exceptions will only be propagated if the thrown
class is the type declared and not a sub-type. Thus "throws Exception"
is no longer valid, the developer will have to declare every type and
sub-type that could be thrown. In GO, this is a given because GO does
not implement inheritence. For GO, the restriction is that interfaces
cannot be used for checked exception declarations and suppression.
2. I decided that one of the reasons people hated checked exceptions
is that they had to declare them for every method. My compromise is to
restrict checking on a public method boundary. I am prioritising the
enforcing of contracts for exposed library methods to enhance
determinism.
3. I decided that the reason for "RuntimeException" was that the
operations which could thrown them were pervasive and unavoidable so
the designers saw that handling every case as a throws declaration
would be cumbersome and compromised by making them invisible. I
decided to solve that problem by introducing a new exception concept
called the "suppressed exception". All my exceptions must be handled
except for a select few errors that are not intended to be handled by
the developer (eg the Error branch of exceptions in Java, eg
OutOfMemoryError). My example is the array index operation "[]" that
could throw IndexOutOfBoundsException. Where the developer has checked
that the index value will never be out of bounds, the developer can
assert that an exception will never be thrown and suppress it so that
the compiler will not require callers to declare it. Thus suppressed
exceptions become unchecked. If, for whatever reason, a suppressed
exception is thrown, the exception would propagate all the way to the
"main" method and cause the program to exit with error. This exception
will not match any catch declaration and is guaranteed to end the
program (after allowing the program to clean up and release resources:
finally, defer blocks). This would defeat determinism except for
compiler and module support I will discuss below.
4. To ensure determinism, the compiler will note every declared and
undeclared exception that could be thrown in the module binary (for
Java, this is the .class file and for GO, the object file). The
compiler will save three lists: declared exceptions, undeclared
exceptions for private methods, and suppressed exceptions. This will
be used by tools to assist the developer by showing all the ways that
a method can end due to exceptions. With this simple addition to the
binary, determinism is ensured.
On Dec 19, 2:25 pm, Vincent Risi <vincent.r...@gmail.com> wrote:
> cannot be done properly at library level. We also agree that all
> exceptions must be caught by the caller even if the caller then elects
> to throw it on, i.e. not 'set jump' 'long jump' aspects to it. I like
I originally agreed with Vince on this point. In fact, I made the case
for it. I did, however, change my mind as explained above.
> the catch being written in the form "This is what I am going to do if
> an exception of this type occurs" before I write the code that could
> result in an exception.
>
> I think the statements like
>
> catch throws db.CorruptDatabase;
> catch fails *IndexOutOfBoundsException;
> endcatch ProcessException;
>
> would be better as
>
> catch db.CorruptDatabase { throw }
> catch IndexOutOfBoundsException { fail }
> catch ProcessException { end }
My reasoning for "catch throws" and "catch fails" instead of "catch
xxx {throw}" and "catch xxx {fail}" is that European language speakers
read left to right and compilers process left to right so it is easier
for the reader and the compiler to understand the intention.
I believe that the form "catch xxx { throw }" is not compatible with
checked exceptions since there is abiguity; is this an exception
declaration or is it rethrowing a caught exception? The "catch xxx
{ throw }" syntax could be thought of as an implicit declaration in
that every exception explicitly thrown in a public function is
implicitly declared as checked. I don't agree with that style. In my
syntax, a "throw" statement within a catch block will rethrow the
caught exception but the developer must still declare the checked
exception in a "throws" clause.
Lastly, I don't allow interfaces in a "throws" clause but I do allow
interfaces to be caught and handled. Thus "catch IException {...}" is
legal but "catch throws IException" is not. How would I enforce my
rule against propogating interfaces when I have a block that rethrows
exceptions as such "catch IException {... code here ...; throw }"? For
that case, using the "throws" syntax, the compiler would list all the
exceptions caught by IException but unchecked as unhandled exceptions
and forcing the developer to declare them.
I hope I have explained my reasoning, please ask away if my
explanation is ambiguous or confusing.
Regards,
- Myron
There is sentiment expressed regarding the Go language of a non-desire
to add exception handling features to the language. Yet at the same
time Go is self-billed as a "systems programming" language, that as C,
would be suitable for writing system software.
Well, writing system software for the 21st century implies the ability
to write operating system kernel code, device driver, or even an
application server that does not blue screen of death or panic out of
the running process, dropping to, say, a crash dump dialog, window, or
command line. This would not be viewed as a user-friendly software
system. Indeed, even most programmers would find such an experience to
be very retrograde.
With Go, all error code checking must be done to perfection and it
must be sure that no array/buffer bounds is ever overrun, divide by
zero fault, etc., as there will be no way to trap on raised exceptions/
faults/traps/interrupts, etc. Hence assembly code would need to be
added as processor specific, in order to deal with such. Plus it would
still be difficult to integrate such assembly code with the Go code so
as to have graceful recovery/resumption of execution.
Microsoft added structured exception handling to their C compiler and
hence it is possible to write all system level code in C while still
being able to code a response to exceptions/faults/traps/interrupts.
In the past I wrote Windows code that used this facility to interact
with the virtual memory paging system.
So goroutines and channels for concurrency are very good, but this
language will really never catch on for systems programming if it does
not provide an integrated language capability to deal with exceptions/
faults/traps/interrupts. Today our software must attempt it's utmost
to run 24/7 and we could really use a lot of assistance from our
language and its runtime toward making that possible.
Stop. Go does not allow you easily to *fail* to perform error checks.
Due to the use of a separate return value to provide the error
information, you need to explicitly ignore the additional return
value, or to record it in a variable, and then fail to check it, both
of which are highly visible at a glance at the code. This is something
I've seen missed repeatedly in arguments asserting that a lack of
exceptions somehow requires perfection from the coders or encourages
people to ignore them, which is not something Go makes easy.
In this manner, Go already assists in the creation of robust systems.
Implying that it makes errors easy to miss or ignore in the manner of
C simply because it lacks exceptions seems wrong, to me. I'm sure
there are other, better arguments for exceptions, but this particular
one, I do not agree with.
From the point of view of the application server none of this matters.
The app server process is going to exit due these runtime faults. Code
that checks for error return code values will never see these
situations to be able to detect and handle them. There is all this
theoretical hand waving of how software that generates such faults
should not continue executing, but in the real world, our users and
clients demand that we write our software to be fault tolerant and
able to continue execution - even in the face of some components that
may be buggy.
A robust app server would use exception handling facilities to catch
such situations. It could then unload the ill behaved component, or
otherwise no longer permit it to execute. The app server stays up and
all the other loaded components continue executing unaffected.
What goes for an app server goes for an operating system or a
windowing desktop manager, or an extensible IDE like Eclipse, or a
telcom server processing VOIP, or server components monitoring and
controling real-world events. In our case we write software that runs
on servers and handles RFID, OCR, VOIP, streaming video, traffic flow
events from pavement loop sensors, it controls gate arms, signal
lights, and processes readings from scale weight devices. None of this
software can be written to such that it simply terminates due to a
panic exception when bad things happen due to some buggy software in
the mix. We write layed software systems that are decoupled and able
to carry on at large even when specific components have failed due to
runtime exceptions. Sometimes these software systems may have three
levels or so of graceful degradation where is able to keep the
customer in operation even though a piece of hardware has failed and
caused a software component to start generating runtime exceptions.
Now if I started writing my software in Go where my software will
always bail out on any such situation, it would not be very successful
software relative to our Java and .NET software. In my industry the
software runs in 24/7 capacity.
I like Go overall, and very much like channels an goroutines. Lack of
intrinsic exception handling will relegate it to niche status.
Is strange Go creators seem to be taking the position they are given
that Erlang addressed both concurrency and fault tolerance. Why did Go
decide to ignore the problem of fault tolerance in terms of language
innovation? That is every bit as important as concurrent programming.
Yet Go is stuck with the same approach that was utilized in the 1970s.
Oh wait, it's coupled with a feature for multiple return values -
okay, a small step forward over the 1970s.
> Stop. Go does not allow you easily to *fail* to perform error checks.
> Due to the use of a separate return value to provide the error
> information, you need to explicitly ignore the additional return
> value, or to record it in a variable, and then fail to check it, both
> of which are highly visible at a glance at the code.
No, this isn't right. For example, fmt.Printf() returns an error that
is widely ignored. You might say this is "highly visible" assuming you
know the function definition, but at the call site, there's no
distinction between a function that doesn't return anything and a
function the returns a value that's ignored. Error-checking in Go is
fairly haphazard, even in the standard libraries.
- Brian
On Dec 19, 4:36 pm, RogerV <rog...@qwest.net> wrote:
> Just want to add this opinion to this thread:
>
> There is sentiment expressed regarding the Go language of a non-desire
> to add exception handling features to the language. Yet at the same
> time Go is self-billed as a "systems programming" language, that as C,
> would be suitable for writing system software.
It's unclear what the Go team means by "system language". So far, it
doesn't seem to mean kernels or device drivers. For an application
server running in a cluster in a data center, "fail fast" is a
reasonable solution; if something goes wrong then there are other
servers to take over. Also, there are usually "baby sitter" processes
to restart the application server if it fails, and a restart is often
the best way to recover from a program gone wrong.
Also, in a larger system, perhaps all communication between subsystems
will happen via channels, rather than function calls, in which case
Erlang-style error-handling might be more appropriate. Maybe what we
really need is a way to monitor, kill, and restart goroutines?
So even if error-handling needs to be improved, it's not at all clear
that it needs to be done now. Somewhat haphazard error-checking seems
to be okay in an experimental language, and the stuff on the roadmap
looks more important.
- Brian
I like exception mechanisms because I'm lazy, and I'd rather handle exceptions than prevent or anticipate them. Generally, though, most of the exceptions I'm handling come from code I didn't write.
Because Go lacks exceptions, it has encouraged me to rethink how I design and implement my code, especially in packages intended for general use. That's a Good Thing(TM).
The best part is that Go's multiple return / multiple assignment capability allows me to carry an error state with minimal effort, and the simple one-line idiom of
if val, ok := function(foo, bar); !ok { error invocation }
allows me to handle the error elsewhere if I desire to (a shortcut to raising an exception, catching it, handling it, then calling the appropriate handler function). Or I can choose handle it locally.
The idiom makes it clear when an error is ignored: "val, _ =" is provided to make that case explicit.
In C, behavior equivalent to this is generally both a mess to implement and difficult to maintain. I suspect exceptions may have initially been created to get out of that mess, and not because exceptions are needed for good programming. That is, exceptions can be viewed as warts added to languages to permit other language limitations to be bypassed.
Go already seems flexible enough to permit the programmer to implement whatever general error handling system an application needs, rather than be constrained to using a single mechanism a language designer thought best.
Furthermore, it seems there is no consensus that a single exception mechanism exists that is clearly best for use with CSPs. Any exception mechanism added today would have a high risk of being wrong or inadequate.
I'm certain the Go language or runtime specification will eventually be extended to provide exception handling. But I suspect it will be implemented in a minimal and very low-level manner, ugly and difficult to use. It would not intended for general programmer use, but would instead be provided as a tool to make it easier to implement a variety of higher-level exception packages. Similar to how semaphores are used to create mutexes and other useful abstractions.
I believe such a tool will be well worth waiting for, but in the meantime it will take lots of experience with the Go language to determine what that tool needs to provide/support, especially where goroutines are concerned.
We are in the "information gathering" phase, so we should attack this issue empirically, from a bottom-up perspective based on actual Go applications, to see which exception mechanisms Go actually needs.
The best approach going forward may be something like this:
1. Write and publish real, useful Go applications, and clearly illustrate in the code where the lack of exceptions imposes an undue cost, with specific suggestions in the comments showing how a particular kind of exception would make a huge difference.
2. Let the Go community hack on the code, to see if the need for exceptions can safely and cleanly be lessened or removed. This will encourage new idioms to be developed and documented, or show that no clean idiom is likely.
3. Apps that still need exceptions would be added to a list of "use cases", creating the requirements that any future Go exception mechanisms would need to support.
Right now, that list is empty! There already are lots of Go applications that work well, and they all work without exceptions.
We need to stop proposing exception mechanisms first, then trying to justify them as being "the one". That's backwards!
-BobC
I wouldn't necessarily argue that Go should do try/throw/catch/finally
in the manner of Java, C#, et al. I would just suggest that the
current situation of checking error codes upon function return is not
sufficient. If fact it really rather sucks just from the point of view
of how code becomes obfuscated by the constant explicit error checking
cluttering everything up. It also grates in that I haven't had to
write that manner of code since the 1980s when I wrote shrink-wrapped
software titles in C. I didn't like it at all then, either. It almost
drives me nuts to see a new language debut in the 21st century and yet
leaps backward several decades to using that same approach.
The problem with the current error handling is that it is not compiler
handled but a library based convention. As happens in all conventions
interpretations become like "broken telephone" messages. Exceptions is
part of most modern programming languages and ignoring the merits of
their use, is quite astounding. To be sure there is no programming
language that is not susceptible to the misuse of idiots and to throw
out exception handling because of this is short sighted. The only
thing I can say about a convention that mixes valid program return
values with an error return value just denies the reason why we write
functions in the first place. I have a square root function which may
raise an error or return a valid value but I cannot use it in a
calculation because it must be coded
value, error := sqrt(-1)
Lets just say this is just horrible and leave it at that.
Instead of having that fruitless debate again, how about we try to get
an RFC that shows how exceptions *would* be integrated into the
language? In any case, that is the first step to any implementation.
Those of you who want exceptions have obvious reasons to participate.
Those who don't want exceptions can also participate constructively:
assuming exceptions, in the most general sense of non-local handling
of exceptional conditions, *are* going to be added, surely you have
some idea of what would displease you least. Who knows, you might even
find that some form of exceptions, when combined with other aspects of
Go, is actually good.
Both "sides" of this have something to contribute. The debate of
whether exceptions should be in Go at all is one that should be had,
but only once it has been clarified what exceptions in Go would
actually mean.
So, please, try to steer back on the topic of how to alter the
language to introduce exceptions, and let's decide whether exceptions
should be added based on the merits and demerits of exceptions *for
Go*, not the general concept of exceptions or the incarnations you may
have seen in C++, Java or whatnot.
My personal preference would be for a condition/handler/restart
mechanism similar to that found in Common Lisp, because it correctly
decouples the handling policy from the recovery logic. Unfortunately,
I don't have time to pursue that and turn it into an RFC of my own,
but it might give you an example of a form of "exceptions" that is
rather unlike what is commonly meant by exceptions. Please, let's
clarify the solution universe before blindly opposing it.
- Dave
I hope we can agree that the Go error idiom works well.
First, let's wrap the above "horrible" example in the minimal Go error idiom (no else clause):
if value, ok := sqrt(-1); !ok { /* handle error */ }
// Continue with normal processing
As an error-tested call, just like any other function, nothing about the above is particularly objectionable. However, what should be done when sqrt() is called within an expression?
value := x / sqrt(b)
Here we have two error sources: 1) When b is negative you get a bad sqrt() call, andwhen b is zero you get division by zero. Today, a Go app with this code would just crash when an error occurred.
But it gets worse: If sqrt() did return an error element, it could not be called within a Go expression! This could be remedied by a wrapper that discards the error return element, but that seems to be a silly thing for a language to force you to do just to gain the powerful "sugar" of arithmetic expressions.
The Go error idiom is very useful, but as a language Go is presently incomplete when it comes to errors:
1. Errors generated within numeric expressions or by numeric operators cannot be detected or handled by user code.
2. Functions that do return an error element cannot be directly called within numeric expressions.
_Function evaluation and error management within expressions is a special case that warrants special attention._ This also involves operators: How can or should an operator return an error? Division by zero is the primary example.
What if it were possible for the Go error idiom to be used with Go expressions?
First, lets look at a traditional minimal exception clause for the above case:
try {
val = x / sqrt(b)
}
catch {
// handle error
}
// Continue with normal processing
The above can be scrunched onto one Go-like line:
try {val = x / sqrt(b)} catch { // handle error }
// Continue with normal processing
And here's the proposed Go idiom for the same statement:
if value, ok := x / sqrt(b); !ok { // handle error }
// Continue with normal processing
When compared to exceptions, the Go idiom doesn't look "horrible" to me. Do you agree? If the language had this capability, to support the Go error idiom for expressions and operators, would it be enough?
Do we need an exception mechanism *primarily* to deal with errors arising within expressions or from operators? That is, if we could access such errors outside of the expression using the familiar Go idiom, would we no longer need exceptions?
There may be a (hopefully simple) language hack that could let us experiment with "numeric expression errors":
1. Numeric functions to be called within expressions must return a single numeric value. A second error return value is optional.
2. If a numeric function explicitly returns an error value, it must be the second value returned.
3. If a numeric function (or operator) does *not* explicitly return an error value, the compiler may, when needed, assume an additional return error element having a value representing "success".
4. Within a numeric expression, each return error element is immediately tested upon each expression element return. The first detected error causes expression execution to abort, in which case all expression assignment elements, other than the final error element, may be set to arbitrary values and/or types. The error return element is set to the error that occurred.
5. If an assignment does not explicitly assign the error return element (to a name or "_"), then the program will crash when an error occurs within an expression (equivalent to conventional exceptions without a catch clause, and to the current Go behavior).
The net effect is to make an "error value" an implicit part of every Go numeric operator, expression and assignment. The user can make this support explicit by using the Go error idiom.
To make this work, we may need the error return element be of a special type which the compiler recognizes and "understands". It may perhaps be a superset of the data content of error.Error combined with something like the implementation of flag.Flag, where the error value is actually a reference to a bool which is the first field within an error struct instance, to permit easy error testing while also supporting detailed error capture and reporting.
Time for an example: Let's say "a/b" is syntactic sugar for "fdiv(a,b)", a function that returns an appropriate error when division by zero is attempted. The compiler would generate code for the statement "value, ok = x / sqrt(b)" as if it were a function returning two values, written somewhat as follows:
value, ok := func {
if val_sqrt, ok_sqrt := sqrt(b); !ok_sqrt { return val_sqrt, ok_sqrt }
return fdiv(b, val_sqrt); // No need to check last call in expr
}
Let's call this the "Rule of Error Return", and it have it apply to all Go numeric operators, functions, and expressions. Hopefully, existing Go code would not be affected, and new code could take full advantage of it.
I can't think of anything *simpler* than the above to permit numeric expression errors to be trapped and tested using the Go idiom.
If this works, would we still need exceptions? If so, please show some code that demonstrates why!
-BobC
if b > 0 { // depending on what class of numbers you are using, this
might not suffice, but I think it makes the point
val = x / "sqrt(b)"
} else {
// handle "error"
}
or
switch {
default: val = x / sqrt(b)
case b > 0:
// handle "error"
}
Essentially, if your code is assuming that input is going to work, but
it might not, thats not a case for exceptions, or errors, it should
take all cases possible input cases into account.
David Anderson wrote:
> So, please, try to steer back on the topic of how to alter the
> language to introduce exceptions, and let's decide whether exceptions
> should be added based on the merits and demerits of exceptions *for
> Go*, not the general concept of exceptions or the incarnations you may
> have seen in C++, Java or whatnot.
Which brings us back to:
On Dec 19, 7:45 pm, Bob Cunningham <FlyM...@gmail.com> wrote:
> We are in the "information gathering" phase, so we should attack this issue empirically, from a bottom-up perspective based on actual Go applications, to see which exception mechanisms Go actually needs.
>
> The best approach going forward may be something like this:
>
> 1. Write and publish real, useful Go applications, and clearly illustrate in the code where the lack of exceptions imposes an undue cost, with specific suggestions in the comments showing how a particular kind of exception would make a huge difference.
>
> 2. Let the Go community hack on the code, to see if the need for exceptions can safely and cleanly be lessened or removed. This will encourage new idioms to be developed and documented, or show that no clean idiom is likely.
>
> 3. Apps that still need exceptions would be added to a list of "use cases", creating the requirements that any future Go exception mechanisms would need to support.
>
> Right now, that list is empty! There already are lots of Go applications that work well, and they all work without exceptions.
>
> We need to stop proposing exception mechanisms first, then trying to justify them as being "the one". That's backwards!
You need practical, real-world use cases in Go in order to be able to
design an exception equivalent tailored for Go. Most of the examples
that are given are contrived based on non-Go style, and situations
where input checking is really more adequate. Some arguments are based
on existing code-bases that throw exceptions you are forced to handle,
when these code-bases can't exist in Go. So proposing mechanisms when
the requirement isn't even adequately defined should be out just as
much as arguing the "shouldness" of exceptions. Its like designing a
bicycle for aliens when you don't even know how many limbs they have,
or have any concept of their anatomy.
I don't know if there is somewhere a well established definition for
this, but I always understood the terms "system programming language"
and "systems programming language" as separate, very much distinct
terms. The first is IMO being a language for writing hardware drivers,
kernels etc., the later a language for writing large amount (business)
data processing with a complex architecture both in the data structure
(s) and in the resources topology/data flow paths - multiple machines
communicating together to reach the desired computation effect.
* System PL - used to make (every single) computer operate
* Systems PL - used to make data processing systems, composed of
potentially many intercommunicating computers, operate.
My ¢2
The compiled code has to support the programmer without making him bow
to convention and not rule. We did suggest that making not handling of
errors a compiler error is more likely to have the programmer be aware
of what can go wrong and insist there is some handling.
> ...
>
> read more »
On Dec 20, 7:10 am, Vincent Risi <vincent.r...@gmail.com> wrote:
> I think that Myron put in an RFC to show a mechanic of how to use
> exceptions in Go. I suspect that Go will be going nowhere while it is
> only marginally useful for writing small utilities. I think the Go
> team has done a good job of what they have done so far and I also
> suspect that adding in exceptions is not a trivial task. I cannot
> really see how goroutines and the users thereof can even hope for
> reasonable persistance with the fact that the code is apt to panic and
> die.
You deal with it by committing persistent data to disks on multiple
servers, by doing enough testing so that it's rare, by having multiple
servers, and by automatically restarting any server processes that
panic.
Perhaps a stronger argument might be made in client-side programming.
Users don't like it when their apps crash on them, and having good
restore on restart only partially mitigates that.
- Brian
The designers of Go have language design goals in mind that are pretty
much driven toward solving an internal Google itch - a better language
than C/C++ for writing certain server software that runs on their back-
end data centers.
Whereas when I saw language of the sort billing Go as a "systems
language" this conjured up the image of how C was used to write much
of the Unix kernel, and much of Linux kernel today. I.e., very
literally a systems programming language.
During the 90s, working at Microsoft, I used their C and C++. They
embellished their version of the C compiler with what they called
structured exception handling. Using this facility, one could
literally write any aspect of system code because any exception,
fault, interrupt, or OS trap mechanism could be dealt with in the C
language without need necessarily to go and write it in assembly
language.
So is looking like the Go language designers have no such aspirations
for their language. They're content to solve the Google itch problem
and rely on watch dog processes that restart their server application
programs when they panic fault.
If that is the extent of aspiration that exist for this language, then
there is no need to do any more than the current error code
conventions. Arithmetic divide by zero is no more likely in buggy code
than the mistaken use of a nil pointer or array index violation (or
underlying hardware failing causing exceptions in the software that it
was never QAed for).
Instead of writing an app server in the manner of Java, where it runs
in a single process in conjunction to any loaded beans, would instead
have to write a Go app server where POSIX fork is relied on to launch
a child process to run any code that might potentially panic fault,
and could thereby be restarted by the parent process acting as a watch
dog.
For myself I'll go away with that understanding and drop the whole
matter.
There's some truth in your claim that Go was designed for writing
servers rather than kernels*, although the leap from there to watchdog
processes is bizarre. It seems that because the language doesn't have
some sort of exception mechanism (yet), you believe we can have only
one model in mind. Both arms of that syllogism are faulty.
This list keeps comparing exception handling to Go's multiple-value
function returns. That's just weird.
We have spent many hours talking about something for Go that would
handle exceptional conditions, but have not found a design that feels
right. Exceptions as defined in languages we have seen do not fit the
existing language; mechanisms along such lines we have used before
don't either. But we are still thinking and talking about them. As
the FAQ says, they remain an open issue.
-rob
* We have talked about kernels too. With some changes to the runtime
it could probably be a nice language for implementing kernels.
Go was designed for certain areas of system programming. By
carefully adding some critical features, Go can be more appealing
to a lot more people and many other areas of programming tasks.
Exception is one such feature. Some people will simply don't
like to program with panic in-mind. This is reality of life. Go may
choose to ignore those people altogether or adding exception to
make more people happy. I will leave the decision to other
people and just give some opinions on the design.
Java exception is widely known. Unless Go can do something
significantly better, I suggest Go stay closer to Java to reduce
learning curve and misuse of new feature.
Since Go already has defer keyword, we can drop finally clause
from Java to make things simpler.
Since Go types are independent, it is not feasible to do hierarchy
exception types, so we should use independent exception types
or just a single exception type with some generic container. I
think a single type is simpler.
type Exception struct {
Name string; // such as "IndexOutOfBoundsException"
Message string; // human reasonable error message.
Cause *Exception; // @nullable, previous exception
Trace string; // stack trace of this exception
Data interface{}; // arbitrary exception data
}
The syntax will be something like this
// expr can be a block here
try e := expr {
// do whatever you care about e or just throw it again.
}
// RuntimeException is a regular function that returns
// type Exception
throw RuntimeException("nothing");
Since Go uses error code in most places, there will
be very few places needs exception. So the syntax
complexity does not really matter, we can just use
Java syntax as is.
One primary difference from Java is exception is value
type. Go can throw exception without heap allocation
if you choose to skip stack trace.
Hong
This is an interesting point. One of the biggest problems
I have had in writing the Mercurial extension for "hg codereview"
is that I cannot tell, looking at a function, whether it might
raise an exception that I should try to handle. Partly this is
because exceptions too often get used for unexceptional
conditions, and partly this is because Python doesn't record
exception information (or anything else) in a static type system,
but mostly I think it is because not programming with error
handling in mind leads to programs that handle errors badly.
I don't see why this behavior should be encouraged.
At the least, I think an exception system would need to
do two things:
1. strongly discourage use of exceptions except
for truly exceptional conditions (e.g., not being able to open
a file is not an exceptional condition).
2. make it clear when code should be thinking about
catching an exception, and which ones to think about.
(this is helped by getting #1 right.)
Note that the current, explicit error handling happens to
do both of these very well.
Russ
Russ, I did cover this exact issue in a previous post. I will send
that post to you for your convenience.
> but mostly I think it is because not programming with error
> handling in mind leads to programs that handle errors badly.
> I don't see why this behavior should be encouraged.
>
I agree 100%. Since I have to build 24x7 systems, working with vendor
libraries that are poorly defined is a nightmare.
> At the least, I think an exception system would need to
> do two things:
>
> 1. strongly discourage use of exceptions except
> for truly exceptional conditions (e.g., not being able to open
> a file is not an exceptional condition).
>
I have to disagree. The exceptional case is contextually driven. For
example: if I write an application that expects certain files to be
present, then when they are not, that is exceptional. It is for that
reason that I should be able to wrap an open statement which returns
an erc into one that throws an exception. Thus I can remove a whole
load of error handling code from my processing code and only address
them in the places where they make sense yet still cover every
scenario. The current code that I have to produce in Go hides my
intention in the noise of error handling. The code is less readable.
Basically, you are preaching a code style in your language and are
trying to solve a social problem by crippling the language and not
even solving that problem in doing so. It's a lose-lose proposition.
Social problems can only be solved using social means. Your code does
proper error handling with error returns because you are putting
effort and thought into your API. I can show you how I can write
programs that return error "information" in various ways that still
leave the user scratching their heads as to what to do with error
handling; I used to be a C programmer and saw many, many examples of
this. In fact, many developers ignore large parts of the UNIX API
possible errors because they don't understand what needs to be done in
that case, or the errors are not always declared and defined in an
understandable way, or the developers are lazy and don't want to write
tomes of code for every possible error scenario so just handle the
obvious cases and ignore the rest.
Whether an error code or struct is "return"-ed or "throw"-n is
secondary to the design of the error code/struct and the API. These
are just two ways of returning the error information. In one case, the
return, you have to test the value after each statement. In
exceptions, the use-case for "if error, return; if error, return; up
the call stack until there is some code to handle the issue " is built
into the language.
As long as the exceptions are never invisible (as is the case in
python/c++/java) I don't see the problem. My solution for invisible
exceptions is in the post I sent you privately.
> 2. make it clear when code should be thinking about
> catching an exception, and which ones to think about.
> (this is helped by getting #1 right.)
>
I agree, that is part of the API design and is a social issue.
> Note that the current, explicit error handling happens to
> do both of these very well.
>
In a manner of speaking, your code does this well. With time, as Go
becomes more mainstream, you will be seeing the bad code and there is
nothing your language can do about it.
Regards,
- Myron
> Instead of having that fruitless debate again, how about we try to get
> an RFC that shows how exceptions *would* be integrated into the
> language? In any case, that is the first step to any implementation.
> Those of you who want exceptions have obvious reasons to participate.
One of the difficult issues which exceptions in Go would have to
address is how to handle an exception thrown in a function started
with a go statement. Requiring every goroutine to set up an exception
handler increases program complexity. Letting an uncaught exception
in a goroutine kill the program means that the general problem remains
unaddressed. The goroutine which started the goroutine which got the
exception may no longer exist. A goroutine could somehow write an
exception on a channel, but that changes exception handling back into
an error checking mechanism.
Many possible choices, none that seem clearly correct.
Ian
One of the difficult issues which exceptions in Go would have to
address is how to handle an exception thrown in a function started
with a go statement.
On Dec 21, 1:00 pm, Ian Lance Taylor <i...@google.com> wrote:
Its not clear if a uniform treatment of exceptions in goroutines and
functions is a sound approach anyway. (It seems to me that) top level
exceptions in goroutines are by nature a specialized category of
interprocess signaling and coordination and regardless of how they are
to be addressed, their consideration should not affect the decisions
regarding the exception handling mechanism for procedural segments.
/R
From a library user point of view, the Go error mechanism is as bad,
if not worse, than that in Java.
For a language to have good error handling, I maintain that it must
satisfy 2 requirements (please forgive my names, I am really bad at
them :)):
1. Contractual determinism
2. Reflective determinism
Contractual determinism is a declaration made by the programmer of all
errors that could be raised/returned from the function which the user
is expected to handle in the normal use-case. For type based error
handling, this means that the programmer declares all the concrete
error types. This is important as the compiler can then be used to
ensure that all errors intended to be handled, are handled. The
benefit is not for the case where the user writes the original
consuming code (reflective determinism is good enough for that) but in
the case where a library is updated/upgraded and additional errors are
declared, the compiler will then inform the user that additional
errors must be handled (or in same cases, where errors have been
removed).
Reflective determinism is when the compiler stores in the binary
module all possible errors that could be returned, even those that are
not declared as intended to be handled in the normal use-case (for
example, in my error handling suggestion where private functions do
not have to declare their errors). This mechanism ensures that there
will never be an invisible error and is intended to be used with
tooling/IDEs.
I hope that these concepts will help understand and produce better
error definition and handling mechanisms.
How would you expect a goroutine function that ends up in an error
state to communicate this issue to a user? I personally would expect
the users of this functions channel to be aware of what gotchas they
can expect and be able to deal with them. How does the current non
exception mechanism help the user deal with goroutines that need to
terminate due to error.
> For a language to have good error handling, I maintain that it must
> satisfy 2 requirements (please forgive my names, I am really bad at
> them :)):
>
> 1. Contractual determinism
> 2. Reflective determinism
Separate from any discussions about Go, I think you are missing one:
long-term maintainability.
For example, checked exceptions in Java empirically suffer from a lack
of long-term maintainability which over time leads them to be dropped
from many large programs. The issue is not that errors are not
handled, it's that the language mechanism of checked exceptions, which
I think is not unrelated to your notion of contractual determinism, is
difficult to maintain. In effect over time exceptions tend to become
unchecked.
Ian
> Its not clear if a uniform treatment of exceptions in goroutines and
> functions is a sound approach anyway. (It seems to me that) top level
> exceptions in goroutines are by nature a specialized category of
> interprocess signaling and coordination and regardless of how they are
> to be addressed, their consideration should not affect the decisions
> regarding the exception handling mechanism for procedural segments.
I don't agree. Go makes it very simple and cheap to start a new
goroutine. I think that is a very important feature to encourage
correct, maintainable and efficient code for multicore processors.
If some form of exception handling is added to Go, it must also make
it easy to write correct and maintainable code (efficient throwing and
catching of exceptions is somewhat less important, though it obviously
can not be extremely inefficient).
When two different aspects of the language are designed to encourage
code to be correct and maintainable, they must work together easily
and naturally. It is not a specialized category. It's not necessary
that the treatment of exceptions thrown out of a goroutine be
precisely uniform with exceptions thrown within a goroutine, but it is
necessary that it be simple and naturally correct by default. I think
that this issue must be considered as part of any exception proposal.
Ian
Today I would expect a goroutine which encounters a known and expected
error to write an error indicator on a channel, and then perhaps call
runtime.Goexit. The question is what should happen if it encounters
an exceptional condition.
Ian
Your proposal for inheriting error channels seems like a good one to
me. An issue to be resolved with any approach along these lines is
that once error channels are built into the language, as this approach
requires, what is the type of those channels? On the one hand,
assuming exceptions, the error channel should carry enough information
that the receiver can easily rethrow the exception if that is the
right thing to do. On the other hand, given a language-defined error
channels, programmers will naturally want the ability to send their
own values on those channels. I think these issues can most likely be
resolved, but they do require some thought.
Ian
One of the difficult issues which exceptions in Go would have toaddress is how to handle an exception thrown in a function started
with a go statement. Requiring every goroutine to set up an exception
handler increases program complexity. Letting an uncaught exception
in a goroutine kill the program means that the general problem remains
unaddressed. The goroutine which started the goroutine which got the
exception may no longer exist. A goroutine could somehow write an
exception on a channel, but that changes exception handling back into
an error checking mechanism.
There's a problem with this, namely that you're either leaking
implementation details or you're required to "wrap" exceptions leading
to Go-like code.
A simple Java example: Say I implement an "IntSet" abstraction using
an array. If I don't check myself, I will now leak "IndexOutOfBounds"
exceptions or whatever they are called, telling people how I
implemented the set (assume I'd have to declare them for this, but
even if you don't you're still leaking the information when it
happens).
The alternative is that I do index checking myself or that I catch
possible array exceptions and produce my own instead. In which case
you're close to what Go already gives you: Check at each level and
return appropriate errors further up.
I like exceptions too, and I think Go would be better off with
exceptions, but I think it's a little harder to find a design that
doesn't repeat previous mistakes. Maybe it's impossible.
--
Peter H. Froehlich <http://www.cs.jhu.edu/~phf/>
Senior Lecturer | Director, Johns Hopkins Gaming Lab
On Dec 21, 2:38 pm, Ian Lance Taylor <i...@google.com> wrote:
I'm pretty much in agreement with you. But the point I am making is
that exceptions in procedural segments lend themselves more naturally
to exception handling formalisms, and that propagation of exceptions
from top level functions in a goroutine are (in effect) IPCs and can
be treated as such. So while for procedural segments we can focus on
try/catch mechanisms, for the goroutines the more natural forms will
probably be in form of (default) error channels and how the compiler/
runtime will package exceptions thrown in a procedural sequence and
propagate them through the exception handling channels.
For exmaple, assume there is some form of exception handling semantics
defined for procedural go. Then for goroutines, a (purely exemplary
and not proposed) form such:
errs := go func() {.....}
would address the handling of uncaught/declared exceptions from the
func() without attempting to force a formally unified approach.
/R
>
> Ian
In Erlang exit signals propagate to 'linked' processes which will also
exit unless they have called trap_exit() in which case they will
instead receive the exit signal as a message in their message queue.
Goroutines don't have a single incoming message queue and the
synchronous and unidirectional nature of channels is also very
different from Erlang's message queues. If a parent goroutine has an
exception can it tell it's child goroutines about it? how would this
work?
Re-spawning an Erlang process is pretty easy(especially if it's a
named process), you re-spawn and everything continues to work.
Re-spawning a goroutine is more tricky, as the goroutine re-spawning
the other dead goroutine needs a reference to the channels it was
using, which means that you pretty much are required to have your
goroutines in a tree structure.
I like Erlang's model for this, but I'm not sure how it can work with
goroutines and channels, they are very different.
> My main problem with Go is not even that I want full-blown
> exceptions, exactly, I just want to be able to trap errors and recover
> from/handle them. If I use a library that divides by 0, I don't want it to
> crash my server, and there's no way to prevent that right now.
I'd much rather that exception handling didn't have user defined
exceptions and instead just handled language defined exceptions such
as divide by zero, null pointer etc. It would result in less
programmers using exceptions for things like flow control or making up
stupid non-exceptional exceptions.
- jessta
--
=====================
http://jessta.id.au
How so? How does specifying the errors (either thrown or returned or
whatever method is used to notify the caller) force me to leak
implementation? I don't have to do this in Java as it stands.
> A simple Java example: Say I implement an "IntSet" abstraction using
> an array. If I don't check myself, I will now leak "IndexOutOfBounds"
> exceptions or whatever they are called, telling people how I
> implemented the set (assume I'd have to declare them for this, but
> even if you don't you're still leaking the information when it
> happens).
>
How does my using arrays internally force me to leak
IndexOutOfBoundsException? For this case, I will manage the array and
I will make sure that I don't go out of bounds; then I will assert
that an IndexOutOfBoundsException will not be thrown by using the
"catch fails IndexOutOfBounds" syntax.
For example, let us say that I have an IntSet as such:
type IntSet struct {
setvalues []int;
}
func NewIntSet (initialSetCapacity int) *IntSet {
cap := initialSetCapacity;
if cap < 1 { cap = 10 }
obj := new (IntSet);
obj.setvalues = make ([]int, cap);
return obj;
}
// Returns the index of the given value if exists otherwise -1
func (self *IntSet) findValue (value int) int {
catch fails IndexOutOfBoundsException;
.. code to find value, making sure bounds are never exceeded ...
}
// Might throw OutOfMemoryError if cannot grow array
func (self *IntSet) addValue (value int) {
catch fails IndexOutOfBoundsException;
... code to add the value to the array
preserving order and growing the array as
necessary ...
}
func (self *IntSet) Add (value int) bool {
idx := self.findValue (value);
exists := (idx != -1);
if !exists {
self.addValue (value);
}
return exists;
}
func (self *IntSet) Contains (value int) bool {
idx := self.findValue (value);
return idx != -1;
}
I am not leaking internal implementation. In this example, I am not
throwing exceptions at all and using return codes.
> The alternative is that I do index checking myself
Of course you would.
> or that I catch possible array exceptions and produce my own instead. In which case
> you're close to what Go already gives you: Check at each level and
> return appropriate errors further up.
That is why I introduced the concept of suppressed exceptions.
The only problem with checked exceptions is in interface definition
and that is something I did not cover in my RFC and subsequent
enhancements. For interfaces, the only way I could think of handling
it is to use the "throws" notation as such"
type ISomething interface {
doSomething () bool throws (Exception1, Exception2, Exception3);
}
Where the exceptions are the only ones that the implementation will be
allowed to throw however, the implementation is not required to throw
any.
Thus the following functions would match the signature:
// no exceptions thrown
func doSomething () bool {
}
func doSomething () bool {
catch throws Exception1;
}
func doSomething () bool {
catch throws Exception2;
}
func doSomething () bool {
catch throws Exception1, Exception2;
}
BUT NOT:
func doSomething () bool {
catch throws Exception1, Exception2, Exception5;
}
Since interfaces in Go are HAS-A rather than IS-A relationships, this
will work well.
Regards,
- Myron
Your proposal for inheriting error channels seems like a good one toSnakE <snake...@gmail.com> writes:
> [1] http://groups.google.com/group/golang-nuts/msg/c034e37b15acb0ac
me. An issue to be resolved with any approach along these lines is
that once error channels are built into the language, as this approach
requires, what is the type of those channels? On the one hand,
assuming exceptions, the error channel should carry enough information
that the receiver can easily rethrow the exception if that is the
right thing to do. On the other hand, given a language-defined error
channels, programmers will naturally want the ability to send their
own values on those channels. I think these issues can most likely be
resolved, but they do require some thought.
I like this idea. Let me try and flesh this out:
* Goroutines each have a "chan os.Error" with cap=1; if code in the
goroutine panics (or possibly in other situations yet to be defined)
the channel receives a single value representing the error condition.
If the goroutine exits normally or via Goexit(), the channel is
closed.
* "go" invocations can be used in expression or statement context
* As expressions, they return the error channel of the new goroutine
* As statements, they cause the any error sent over the error channel
of the new goroutine to cause a panic in the current goroutine.
This maintains the current behavior of a process-wide panic if any
routine panics, because any issue with propagate all the way to the
main goroutine unless someone explicitly requests an error channel by
using the errs := go func() notation or similar. On the other hand,
it allows one to construct Erlang-style "monitor" goroutines as
needed. I think this would cover a lot of the use cases for
exceptions; if extended via a Goabort(os.Error) or similar it would go
even further.
--
Walter Mundt
walte...@codethink.info
>> errs := go func() {.....}
>
> I like this idea. Let me try and flesh this out:
http://code.google.com/p/go/issues/detail?id=217
-rob
On Dec 19, 2:30 am, Myron Alexander <myron.alexan...@gmail.com> wrote:
> Brief proposal for exceptions in GO. I am being very brief so I don't
> write a
> tome. I will watch the thread and answer any questions for
> clarification.
[snip]
Maybe I've missed something in your proposal, but could you please
elaborate a bit more on how would proposed exceptions work with
generic/abstract code? Consider this example:
func for_each(a []int, action func(int) int) {
for i := 0; i < len(a); i++ {
action(a[i])
}
}
"for_each" doesn't throws anything by itself but should be transparent
in regard to exceptions thrown from "action". How would you declare
and use such functions?
On Dec 22, 12:10 am, Myron Alexander <myron.alexan...@gmail.com>
wrote:
> On Dec 21, 10:19 pm, Peter Froehlich <peter.hans.froehl...@gmail.com>
> wrote:
>
> > On Mon, Dec 21, 2009 at 1:47 PM, Myron Alexander
>
> > <myron.alexan...@gmail.com> wrote:
> > > Contractual determinism is a declaration made by the programmer of all
> > > errors that could be raised/returned from the function which the user
> > > is expected to handle in the normal use-case.
>
> > There's a problem with this, namely that you're either leaking
> > implementation details or you're required to "wrap" exceptions leading
> > to Go-like code.
>
> How so? How does specifying the errors (either thrown or returned or
> whatever method is used to notify the caller) force me to leak
> implementation? I don't have to do this in Java as it stands.
I guess you know that, but anyhow, in Java IndexOutOfBounds is a
subclass of RuntimeException and you don't need to specify it in class
interface. Making it a checked exception forces you to either suppress
this exception or expose it in your interfaces.
>
> > A simple Java example: Say I implement an "IntSet" abstraction using
> > an array. If I don't check myself, I will now leak "IndexOutOfBounds"
> > exceptions or whatever they are called, telling people how I
> > implemented the set (assume I'd have to declare them for this, but
> > even if you don't you're still leaking the information when it
> > happens).
>
> How does my using arrays internally force me to leak
> IndexOutOfBoundsException? For this case, I will manage the array and
> I will make sure that I don't go out of bounds; then I will assert
> that an IndexOutOfBoundsException will not be thrown by using the
> "catch fails IndexOutOfBounds" syntax.
But how you can be sure that you don't go out of bounds? What is the
purpose of IndexOutOfBoundsException if you can always be sure that
you can't make a mistake? Moreover if you happen to be wrong throwing
this exception will terminate entire application which might not be
always desirable. There are plenty of different errors which are
caused by bugs in a software (like NullReferenceException and
ArgumentException in .NET or std::logic_error in C++) which could
happen virtually everywhere and which you might want to handle
somewhere at upper level.
[snip]
> > The alternative is that I do index checking myself
>
> Of course you would.
Are you supposing that nobody ever makes a mistake and writes bug-free
code always?
[snip]
Best Regards,
Konstantin.
I copy Objective-C memory handling concept of pools (how retain pools
behave), which is:
* You can start an error pool anywhere - this is a channel, which is
managed by system to be always accesible
* When you have started an error pool, all errors sent go to that pool
* When starting goroutines, error pool will be carried to them, too
* This could be considered if there is a need for error-type specific
pools or is this better to have them wrapped somewhere
* To make this concept more general, there could be also non-error
pools
I start from const-type pools, then go to type declarations..
Error pools should be set up like that, semantically:
var a chan int;
...
errorpool += a; // Errorpool is a kind of global variable in thread's
scope
Now, "errorpool" variable will be carried to all functions in this
routine, also to routines started from here. All other threads have
their own error pool unless "a" is given them.
Sending an error 5 (imagine there being some error structure instead
of integer):
a <- 5;
Error pool should have optional method, which could gather runtime
information:
type ErrorPool chan int;
func (e *ErrorPool)onThrow(error interface{}) (catched bool, continue
bool) {
reflection.getCallStack();
// Remove "onThrow" from this stack
// If this wasn't int, add stack to error
e <- error;
return true, false; // No need to send it to parent pool
}
Inside method:
errorpool <- 4; // Will execute "onThrow", then send error
Registering pools:
errorpool = a; // Registers top-level pool
Inside routine or function:
errorpool += a; // Registers pool, which catches all errors and might
forward them to toplevel pool; this pool should be gone when channel
"a" is closed
for example:
errorpool = handler;
divider();
func divider() {
errorpool += divhandler; // Sets up local handler, which does not
collect any stack trace and handles division by zero errors
defer errorpool -= divhandler;
4 / 0; // Will throw an error, call "onThrow" of divhandler and be
blocked until it finishes
}
func (e *ErrorPool)onThrow(error interface{}) (catched bool, continue
bool) {
switch error.(type) {
case divbyzero:
e := error.(divbyzeroerr);
if e.divider == 0 {
e.result = Inf;
return true, true; // Thrower will continue and return
infinity as result of division by zero
}
return true, false; // Thrower will stop
}
reflection.getCallStack();
// Remove "onThrow" from this stack
// If this wasn't int, add stack to error
e <- error;
return false, false; // Send it to parent pool
}
A thing to think here:
Maybe there is no need for this "interface", but there could be a map
of those pools (I would like it a lot more as those could be usable
for other things). Name of poolmap is # in this example:
#[chan int] += a; // Registers channel a to "chan int" pool.
Unregistering is deferred automatically?
#[chan int] = a; // Caller functions would have whatever they had in
"chan int" pool, but this function now has "a"; anyway, if catcher of
it is defined and returns false, then parent #[chan int] will handle
the case
#[chan int] <- b; // b will be sent to a
c := <-a; // c will be read from this error pool and handle
appropriately (as onThrow hasn't forwarded it and has possibly
gathered stack trace etc.)
Ant to make things simpler:
type errorchan #[chan int]; // Would register "chan int" to be
accessible by "errorchan" variable
errorchan internally:
* When something is registered with +=, it will store a pointer to
"parent" in new empty map and put newly registered one there
* When something is sent, which is not yet in this empty map, but
exists in parent, it will be forwarded to parent; it might be that it
will also be registered in map of that branch with notice that if this
channel is unregistered, it will be deleted
* When subroutine is called, parent's map will be marked immutable for
+= (not for -=) [?] and when += is used, new map is created. That to
simulate something, what is maybe not worth simulating at all :)
// First definition of divbyzerostruct:
type divbyzerostruct struct {
divider float;
result float;
}
type divbyzeropool chan divbyzerostruct;
func (e *divbyzeropool) (error interface{}) (catched bool, continue
bool) {
e = StandardError{"Division by zero", error}; // Error will be
forwarded as standard error if there is no registered zero-division
catcher
return true, false;
}
// Creating custom catcher
type divbyzeroisinfinitypool divbyzeropool;
func (e *divbyzeropool) (error divbyzeroisinfinitypool) (catched bool,
continue
bool) {
if error.divisor == 0 {
error.result = Inf;
return true, true;
}
return true, false;
}
Used as in:
a := make(divbyzeroisinfinitypool);
#[divbyzeropool] += a;
go func() {
for e <- a {
// Do something with this error
}
}
// Creating thrower
func divider(a, b float) float {
if b == 0 {
r := #[divbyzeropool] <- divbyzerostruct{0, 0}
return r.result; // Returns result given by error handler
// Without r :=, it's impossible to continue this method, thus
error handler might have input boolean about if it's possible to
continue
On Dec 22, 9:05 am, bork <koznobik...@gmail.com> wrote:
> Maybe I've missed something in your proposal, but could you please
> elaborate a bit more on how would proposed exceptions work with
> generic/abstract code? Consider this example:
>
> func for_each(a []int, action func(int) int) {
> for i := 0; i < len(a); i++ {
> action(a[i])
> }
>
> }
>
> "for_each" doesn't throws anything by itself but should be transparent
> in regard to exceptions thrown from "action". How would you declare
> and use such functions?
You have brought up a good point, one I did not consider.
In your example, what happens if action has an error? I notice that
you are not testing the return value (even though it can return an
int). Can you provide an example with an error and how it would
propagate?
In your function specification, the action can only return an int an
error code (I assume). I did make a proposal earlier on for interface
definition using the throws keyword along the lines of:
type xxx interface {
SomeFunction (some parameters) someReturnTypes throws Exception1,
Exception2 etc {
}
}
So lets say that the action is intended not to throw anything, then:
func for_each(a []int, action func(int)) {
for i := 0; i < len(a); i++ {
action(a[i])
}
}
Or, if it intended to throw, say a ConstraintViolationException:
func for_each(a []int, action func(int) throws
ConstraintViolationException) {
for i := 0; i < len(a); i++ {
action(a[i])
}
}
and so on.
I would still like an example of how you would handle generic errors
in the for_each call. If you can provide me with good example, I could
maybe come up with a better solution for exceptions.
Regards,
- Myron.
On Dec 22, 9:40 am, bork <koznobik...@gmail.com> wrote:
> But how you can be sure that you don't go out of bounds? What is the
> purpose of IndexOutOfBoundsException if you can always be sure that
> you can't make a mistake? Moreover if you happen to be wrong throwing
> this exception will terminate entire application which might not be
> always desirable. There are plenty of different errors which are
> caused by bugs in a software (like NullReferenceException and
> ArgumentException in .NET or std::logic_error in C++) which could
> happen virtually everywhere and which you might want to handle
> somewhere at upper level.
>
I have implemented plenty container classes in my time as a C, C++ and
Java programmer. It is very simple to ensure that your underlying
array is not misused as you have 100% control over it. If I made a
mistake, it would be very easy to pick up on testing. For example,
using the D programming language, I make full use of the design by
contract features to test every incoming value and outgoing value. In
Java, I make good use of asserts. Then I test my code to death. I
generally break up my programs into discrete units and test each one
individually. I take my testing very seriously.
With a suppressed exception (catch fails syntax), if my testing did
not catch the bug, the program will exit with error, after running all
the defers up the call stack to the main entry point. There is nothing
any programming language and runtime can do for unhandled errors.
Compare my suggestion to the current Go implementation; when I make a
mistake and allow an array to be addressed out of bounds, the program
panics and exits immediately without executing the defers. I consider
that the worst option.
>
> > > The alternative is that I do index checking myself
>
> > Of course you would.
>
> Are you supposing that nobody ever makes a mistake and writes bug-free
> code always?
>
No, but that is not something that any language can do anything about.
See my comments above.
Regards,
- Myron.
On Dec 22, 1:23 pm, Myron Alexander <myron.alexan...@gmail.com> wrote:
> On Dec 22, 9:05 am, bork <koznobik...@gmail.com> wrote:
>
> > Maybe I've missed something in your proposal, but could you please
> > elaborate a bit more on how would proposed exceptions work with
> > generic/abstract code? Consider this example:
>
> > func for_each(a []int, action func(int) int) {
> > for i := 0; i < len(a); i++ {
> > action(a[i])
> > }
>
> > }
>
> > "for_each" doesn't throws anything by itself but should be transparent
> > in regard to exceptions thrown from "action". How would you declare
> > and use such functions?
>
> You have brought up a good point, one I did not consider.
>
> In your example, what happens if action has an error? I notice that
> you are not testing the return value (even though it can return an
> int). Can you provide an example with an error and how it would
> propagate?
Returning int was just a typo. A sample was written assuming there
will be some kind of exceptions support, currently it is most probably
would be written like this:
func for_each(a []int, action func(int) os.Error) os.Error {
for i := 0; i < len(a); i++ {
err := action(a[i]);
if err != nil {
return err;
}
}
return nil;
}
However, here we have pretty much the same problem as with checked
exceptions. We have constrained our generic code to work with actions
returning os.Error only which would be even worse when Go will have
generics (one more advantage of exceptions, btw).
>
> In your function specification, the action can only return an int an
> error code (I assume). I did make a proposal earlier on for interface
> definition using the throws keyword along the lines of:
>
> type xxx interface {
> SomeFunction (some parameters) someReturnTypes throws Exception1,
> Exception2 etc {
> }
>
> }
>
> So lets say that the action is intended not to throw anything, then:
>
> func for_each(a []int, action func(int)) {
> for i := 0; i < len(a); i++ {
> action(a[i])
> }
>
> }
>
> Or, if it intended to throw, say a ConstraintViolationException:
>
> func for_each(a []int, action func(int) throws
> ConstraintViolationException) {
> for i := 0; i < len(a); i++ {
> action(a[i])
> }
>
> }
>
And here you made a very important note:
> and so on.
Yes, it looks like we'll have to either duplicate for_each
implementation for all kinds of actions (throwing different
exceptions) or make wrappers tunneling action-specific exceptions into
something common, or discard checked exceptions by declaring every
function throwing some generic error interface.
>
> I would still like an example of how you would handle generic errors
> in the for_each call. If you can provide me with good example, I could
> maybe come up with a better solution for exceptions.
There is no way to handle generic errors in the for_each for now (in
Go), but in e.g. C# it might look like this:
public void ForEach<T>(this IEnumerable<T> items, Action<T> action)
{
foreach(var item in items)
{
action(item);
}
}
public void DoSmth()
{
var items = this.QueryItems();
items.ForEach((x) => x.DoSmth());
}
public void DoSmthOther(ISmthOther i)
{
var items = this.QueryItems();
try
{
items.ForEach((x) => i.DoSmthOther(x));
}
catch (SomeOtherActionException e)
{
// handle this other action exception.
}
}
Best Regards,
Konstantin.
On Dec 22, 1:35 pm, Myron Alexander <myron.alexan...@gmail.com> wrote:
> Konstantin,
>
> On Dec 22, 9:40 am, bork <koznobik...@gmail.com> wrote:
>
> > But how you can be sure that you don't go out of bounds? What is the
> > purpose of IndexOutOfBoundsException if you can always be sure that
> > you can't make a mistake? Moreover if you happen to be wrong throwing
> > this exception will terminate entire application which might not be
> > always desirable. There are plenty of different errors which are
> > caused by bugs in a software (like NullReferenceException and
> > ArgumentException in .NET or std::logic_error in C++) which could
> > happen virtually everywhere and which you might want to handle
> > somewhere at upper level.
>
> I have implemented plenty container classes in my time as a C, C++ and
> Java programmer. It is very simple to ensure that your underlying
> array is not misused as you have 100% control over it. If I made a
> mistake, it would be very easy to pick up on testing. For example,
> using the D programming language, I make full use of the design by
> contract features to test every incoming value and outgoing value. In
> Java, I make good use of asserts. Then I test my code to death. I
> generally break up my programs into discrete units and test each one
> individually. I take my testing very seriously.
I do not have any doubts in your qualifications and ability to write
good code. My point (not only my to be honest) is that it's impossible
to obtain a 100% guarantee that you have a bug-free code. It's not so
simple for container classes and it could be much trickier in other
cases. I assume that presence of software errors is a part of reality
we have to live in and deal with. Sometimes it's ok to close an
application when they are detected sometimes it's not.
>
> With a suppressed exception (catch fails syntax), if my testing did
> not catch the bug, the program will exit with error, after running all
> the defers up the call stack to the main entry point. There is nothing
> any programming language and runtime can do for unhandled errors.
That's might be true, but there are a lot of things developers can do
when they handle errors. A simple desktop application might display a
message box suggesting sending crash dump to developers and restart. A
server application catching such kind of error during serving some
request might log it and/or notify administrators and continue serving
other requests. There could be a lot of reasonable things an
application can do upon detecting a bug.
> Compare my suggestion to the current Go implementation; when I make a
> mistake and allow an array to be addressed out of bounds, the program
> panics and exits immediately without executing the defers. I consider
> that the worst option.
Surely your proposal is a big step forward in many regards, but why
not do it better yet?
>
>
>
> > > > The alternative is that I do index checking myself
>
> > > Of course you would.
>
> > Are you supposing that nobody ever makes a mistake and writes bug-free
> > code always?
>
> No, but that is not something that any language can do anything about.
> See my comments above.
It can throw an appropriate exception giving developer a chance to do
something (like in .NET or Java) or crash saving enough information
for a developer to pin-point and fix an error (like C and C++).
Best Regards,
Konstantin.
Agree. One should be able to catch the panic - if something fails
inside reading a file, then in fact file reading fails.
I have had a really great trouble with Eclipse when I did install a
plugin, which did create wrong kind of file structure - I couldn't
start eclipse to remove the plugin. Happily I found out that this
plugin was installed to user space instead of global space and was the
only plugin there, so I did delete the folder and Eclipse did some
kind of "reinstall" without anything changing for me. But if some
plugin fails, application should run anyway.
I agree. In any language, regardless of thrown or returned errors,
without generics all errors will have to be wrapped in a common error
structure.
Regards,
- Myron.
On Dec 22, 4:35 pm, Myron Alexander <myron.alexan...@gmail.com> wrote:
> Konstantin,
Myron,
>
> I agree. In any language, regardless of thrown or returned errors,
> without generics all errors will have to be wrapped in a common error
> structure.
This issue is somewhat orthogonal to a presence/absence of generics.
When you don't have exceptions then absences of generics is only part
of the problem, since you'll anyhow need some way to find out which of
return values indicate an error and how to propagate them correctly.
With exceptions (if they are not checked) you don't need generics at
all for generic error propagation. My C# example could be repeated
for .NET 1.1 without much changes:
public delegate void Action(object o);
public void ForEach(IEnumerable items, Action action)
{
foreach(object item in items)
{
action(item);
}
}
So I don't see how adding generics would help checked exceptions avoid
loosing generality. I think they require some special approach for
solving this issue.
The current error handling system is like having a separate catch for
every line of code you are writing, besides the fact that it is based
on convention and is not based on rule.
The Mechanics for handling exceptions that was proposed by Myron is
that the compiler is at all times aware of what exception a function
can throw and if any of the possible exceptions are not being caught
by the caller of the function, this results in a compile error, thus
making the programmer aware of what is at stake when he calls a
function. The major benefit of having a form of exception handling
built into the compiler is that the use of error returns are not being
disallowed, but they are not trying to fold every conceivable problem
into an os.Error.
I foresee that the compiler would return an error state and if that
state is ok values that can be returned by the function. This would be
done without the programmer have to return both at the same time just
either the one or the other. Similarly I would expect when a channel
is return data from a goroutine, an internal state is also returned so
that if the goroutine is failing for any reason the user of it is made
aware.
If the calling function is not equipped to handle the error then it
may choose to pass it back to its caller. Of course it could choose to
have the program fail as well, hopefully elegantly handling the defers
and possibly the failure only defers along the way.
If we look at the current state of c++ we see the problems of
convention gone wrong.
A case like that should be fixed by fixing the spreadsheet program,
not by fixing the language.
For any specific implementation of any construct, it is possible to
develop a maximally bad example. What one must consider is the
efficiency of the common case and the limits on the worst case. It's
rarely useful to ensure that the worst case, which is by definition
uncommon, is efficient.
Ian
Qtvali <qtv...@gmail.com> writes:
A case like that should be fixed by fixing the spreadsheet program,
not by fixing the language.
For any specific implementation of any construct, it is possible to
develop a maximally bad example. What one must consider is the
efficiency of the common case and the limits on the worst case. It's
rarely useful to ensure that the worst case, which is by definition
uncommon, is efficient.
> Checked exception thrown to indicate that a string could not be parsed as a URI reference.
Noone in Java world is going to check the string - common practice is to try it, then catch exception and show some error without traceback.> This exception is thrown when an XML formatted string is being parsed into ModelMBean objects or when XML formatted strings are being created from ModelMBean objects. It is also used to wrapper exceptions from XML parsers that may be used.
public class MimeTypeParseException
A class to encapsulate MimeType parsing related exceptions
Again, email reader won't show back trace there, but will handle it cleanly.Thanks for your comments. I am enjoying your challenges and making me
think is helping to flesh out the concept.
On Dec 22, 4:29 pm, bork <koznobik...@gmail.com> wrote:
> This issue is somewhat orthogonal to a presence/absence of generics.
> When you don't have exceptions then absences of generics is only part
> of the problem, since you'll anyhow need some way to find out which of
> return values indicate an error and how to propagate them correctly.
> With exceptions (if they are not checked) you don't need generics at
> all for generic error propagation.
My comments were made in the context of checked exceptions/returns.
> My C# example could be repeated
> for .NET 1.1 without much changes:
>
> public delegate void Action(object o);
>
> public void ForEach(IEnumerable items, Action action)
> {
> foreach(object item in items)
> {
> action(item);
> }
>
> }
>
> So I don't see how adding generics would help checked exceptions avoid
> loosing generality. I think they require some special approach for
> solving this issue.
>
The problem with unchecked exceptions is that they belong in the
domain of dynamic languages since unchecked exceptions are dynamic
exceptions, at least from the caller's point of view. In a true static
type compiled language, all types are declared (explicitly or
implicitly) and checked.
Here is the problem. Given a function:
int GetInt (string columnName);
Are exceptions thrown? And if so, what exceptions could be thrown?
How about this one:
GetInt (columnName string) (int, os.Error);
Can you list the errors that could be returned?
Here is another from Java perspective:
int GetInt (string columnName) throws SqlException;
Can you list all the exceptions (including sub-types) that could be
thrown?
Or this from an unspecified language implementing my proposal (ie only
the exact type thrown must be declared, no subtypes and no
interfaces):
int GetInt (string columnName) throws InvalidTypeException,
RowUnavailableException, ColumnNotFoundException,
ConnectionInvalidException, CommunicationFailedException;
Can you list all the exceptions that could be thrown?
Moving on from checked exceptions, I have been thinking about my
stance on suppressed exceptions and believe you are right, a
suppressed exception should be catchable.
The adjusted proposal would be to change the "catch fails" to "catch
suppress".
If a suppressed exception is thrown, then the runtime will wrap it in
the "SuppressedError" runtime error. Since it is a runtime error, it
is not declared.
As an aside, one thing I did not mention in my RFC was that runtime
errors are not caught by interfaces, you have to specify the exact
runtime error name. For that reason I am trying to minimize the list
of runtime errors so the only runtime errors are: OutOfMemoryError and
SuppressedError.
I have been thinking about function literals and decided that as Go
includes the return types in the function literal, then my proposal
should treat function literals as interfaces thus you would have to
declare it as for interfaces using the throws statement:
func DoSomething (getValue func (columnName string) int throws
InvalidTypeException, RowUnavailableException,
ColumnNotFoundException, ConnectionInvalidException,
CommunicationFailedException) {
.....
}
You might want to create a type for that:
type GetIntLiteral func (string) int throws InvalidTypeException,
RowUnavailableException, ColumnNotFoundException,
ConnectionInvalidException, CommunicationFailedException;
so you can:
func DoSomething (getValue GetIntLiteral) int {
....
}
As per my spec on interfaces, the implementation does not need to
throw any of the exceptions listed so:
x := func (columnName string) int {
return 42;
}
is a valid implementation of GetIntLiteral. Or, if only one type of
exception is thrown:
x := func (columnName string) int {
catch throws ColumnNotFoundException;
if "void" == columnName {
throw NewColumnNotFoundException (...);
}
return 42;
}
is a valid implementation of GetIntLiteral.
BTW, if you would return the function literal x in an interface{}, it
would have type:
func (string) int throws ColumnNotFoundException;
Regards,
- Myron
That is a strong statement that has a definite smell of religion about
it. Particularly given that there are a lot of static type compiled
languages, but to the best of my knowledge the _only_ one with checked
exceptions is Java. (I guarantee that it was not accidentally omitted
from all of the others.) Which tells me that, your blanket assertions
notwithstanding, checked exceptions are NOT a central feature for
static typed languages. And if that is clear to someone with my
relatively limited experience, think about how blindingly obvious it
must be to the core Go developers.
Now I know that you said you were not going to pay any attention to
anyone who doesn't like checked exceptions. However that would seem
to include the core Go developers. If you aren't going to pay
attention to what they said in their FAQ, how are you going to
convince them that you have a good idea?
If you want any chance of convincing others, I strongly suggest that
you try to understand the opposing position so that you can figure out
what arguments might be convincing. For example go to
http://www.google.com/search?q=checked+exceptions and look at some of
the links that take an opinion. For instance read
http://www.artima.com/intv/handcuffs.html and find out why the core C#
team, despite liking the basic idea of checked exceptions, chose to
leave them out of C#.
Once you've digested those ideas, then instead of asserting how great
checked exceptions are you can actually acknowledge what people don't
like about them and explain how those are non-issues for your
proposal.
Regards,
Ben
I would appreciate it if you would not make this personal. It is
obvious that you not only disagree with my points but disagree with me
personally. I respect your difference of opinion but if you are
intending on starting a flame war, please leave.
Regards,
- Myron.
On Dec 23, 1:46 am, Ben Tilly <bti...@gmail.com> wrote:
> On 12/22/09, Myron Alexander <myron.alexan...@gmail.com> wrote:> On Dec 22, 4:29 pm, bork <koznobik...@gmail.com> wrote:
> [...]
> > The problem with unchecked exceptions is that they belong in the
> > domain of dynamic languages since unchecked exceptions are dynamic
> > exceptions, at least from the caller's point of view. In a true static
> > type compiled language, all types are declared (explicitly or
> > implicitly) and checked.
>
> [...]
>
> That is a strong statement that has a definite smell of religion about
> it. Particularly given that there are a lot of static type compiled
> languages, but to the best of my knowledge the _only_ one with checked
> exceptions is Java. (I guarantee that it was not accidentally omitted
> from all of the others.) Which tells me that, your blanket assertions
> notwithstanding, checked exceptions are NOT a central feature for
> static typed languages. And if that is clear to someone with my
> relatively limited experience, think about how blindingly obvious it
> must be to the core Go developers.
>
> Now I know that you said you were not going to pay any attention to
> anyone who doesn't like checked exceptions. However that would seem
> to include the core Go developers. If you aren't going to pay
> attention to what they said in their FAQ, how are you going to
> convince them that you have a good idea?
>
> If you want any chance of convincing others, I strongly suggest that
> you try to understand the opposing position so that you can figure out
> what arguments might be convincing. For example go tohttp://www.google.com/search?q=checked+exceptionsand look at some of
> the links that take an opinion. For instance readhttp://www.artima.com/intv/handcuffs.htmland find out why the core C#
That was a nice read. And you can certainly see students in Intro Java
discovering empty catch clauses very early, even if you *don't* teach
them about try/catch at all. :-D
--
Peter H. Froehlich <http://www.cs.jhu.edu/~phf/>
Senior Lecturer | Director, Johns Hopkins Gaming Lab