Assertion Like Exceptions
=========================
This is a proposal for a rework to the existing exception system.

Motivation
----------
The current exception handling mechanism (EHM) is functional and has wide
use cases. However, there is a certain awkwardness to using them,
particularly the exceptions themselves.

Some of this is just feature incompleteness. But some of it comes down to
a paradigm mismatch. Traditional exceptions carry data and behaviour from
throw to catch - that is, they are objects - to allow for the transfer and
handling of the exception, but CFA does not have an OO design.
Now there are ways to at least lessen this strain (see `Exception.hfa`), but
those tend to limit functionality or are dependent on one or more upcoming
works (such as closed traits or modules).

Instead of trying to solve that fundamental mismatch, this proposal tries to
create exception handling that does not depend on OO-style features.
This restructures the EHM to try and use CFA's existing design patterns.

Design
------
To make exceptions fit better into the rest of Cforall, what if we reused
some of the design that is already in the language: assertions.
The result is a system of checked exceptions that could serve as at least a
partial replacement for the current exception handling mechanism and
exception declarations.

These exceptions work much like existing exceptions, they are raised from a
throw, traverse up the stack to a handler and run the handler.
Where control continues after the routine can vary to mimic both termination
and resumption throws.

An exception is formatted like a function call: `RetType Name(ArgTypes...)`.
The `Name` is a label only used for matching, the ArgTypes give the types of
the data passed from the throw to the handler and RetType can give the type of
the data passed from the handler back to the throw. RetType can also be
`void` to reply with no data, or a noreturn marker to say that control cannot
return to the throw.

This exception signature is usually only written out in full as part of
a function declaration, which says that this function may throw that
exception. Throws and catches will usually replace the ArgTypes with the
arguments or parameters to hold those values.

The actual termination vs. resumption divide comes from the handler. The
handler may `resume`, if it is handling a void or value exception, which
returns control to the throw creating a resumption. If controls runs out
of the handler block, it continues at the end of the try statement after
unwinding the stack.

Throwing expressions (throws or a throwing function calls) must be inside
a function that can throw the exception, or within a catch clause that can
handle it.

There is no case for unhandled exceptions because of the assertion-like part.
Functions are annotated with the exceptions that they throw (thrown within
the function or a called function and not handled). By ensuring that `main`
throws no exceptions, that means all throws will be matched to a try.

Syntax and Usage
----------------
The throws annotations is new a syntax. The design is not final, but for
examples we will use: `throws (EXCEPTIONS)`, where EXCEPTIONS is a comma
separated list of exception signatures. This can happen within the scope of a
forall clause so it can be polymorphic with the rest of the function.

There is no syntax to declare exceptions, although one could be added, they
are effectively declared in the throws annotations.

Throw statements remain, but now they are joined by throw expressions. The
throw expressions return their response value. Throw statements can
(Throw expressions with a response of `void` can be a void expression, in the
few places where a void expression is allowed.)
```
throw Name(Exprs,...)
```
Along with the addition of the expression form, the main change is that the
exception is not an expression, but a literal name and a series of
expressions.

Try statements are the same as the current design except for the catch clauses.
```
catch Name(Arguments...) { ... }
```
Exceptions thrown in the try block are checked for matching handlers. The
match is simply preformed by label name. (Although with type annotations on
the arguments or for the return type, could be extended to involve full
resolution on the signature of the exception.) When an exception is thrown
the matching catch is put onto the stack and run. Running off the end, or
otherwise exiting the catch block, causes a termination and unwinds the
stack.

In addition there is a new statement that can only be used inside a catch
statement (and only in one that can return to the throw).
```
resume [EXPR];
```
This is like a return statement, the expression is required if the exception
expects a value in response. If the expression is present, it is evaluated
before the resume occurs. When the resume occurs, we exit the handler and the
throw finishes execution, resulting in the evaluated value as appropriate.

### Exception Signature Polymorphism
We have everything to make a functional exception handling mechanism.
However, to get a flexible (especially in regards to higher order functions)
system we need a basic level of polymorphism.

```
forall(T, [N], exception E) throws(E)
void foreach(void op(T &) throws(E), array(T, N) & data) {
  for (i; N) op(data[i]);
}
```

A new type of polymorphic parameter is added `exception` which is polymorphic
not on an exception, but a possibly empty of exceptions.
A set of exceptions is can be used in an exception signature in the same
way a single exception can be and are traced from callee to caller in the
same way a single exception is.
They do not interact with throws or catches as those work on concrete types.
At the top and bottom of the polymorph call stack concrete functions will
throw and catch the exception, passing through the polymorphic section.

Examples
--------

### Noreturn Example (Enumeration succ)
A simple example from the enumeration traits, there is a `succ_unsafe`
function in the Serial trait that is wrapped by a `succ` function that adds
some checks and aborts if they fail.

Instead of aborting, it could have an assertion-like exception:
```
int succ(int value) throws(noreturn out_of_range()) {
    if (value == MAX) {
        throw out_of_range();
    }
    return value + 1;
}
```
This serves the same role as the existing function, but combines the succ
with a range check the caller can respond to.

For an example of catching this, consider creating a wrapper to get the
current succeed or abort implementation:
```
forall(T | { T succ(T) throws(noreturn out_of_range(); })
T succ_abort(T val) {
  try {
    return succ(val);
  } catch out_of_range() {
    abort("Attempt to succ to non-existant successor.");
  }
}
```

### Value Resume Example (lookup)
For an example of catching and resuming with a value, consider a map lookup
that throws when you attempt to lookup a key not in the map and a wrapper
that provides a default value.
```
forall(K, V | relational(K)) throws(V NotFound(K &));
V lookup(map(K, V) const & mapping, K & key) {
  // Internal code equivilent too:
  node(K, V) * node = lookup_internal(mapping, key);

  return ((node) ? node->value : throw NotFound(key));
}

forall(K, V | relational(K))
V lookup(map(K, V) const & mapping, K & key, V & defaultValue) {
  try {
    return lookup(mapping, key);
  } catch NoFound(_key) {
    resume defaultValue;
  }
}
```

This may not be the most efficient way to implement a lookup-with-default
function, but it would work. Practically, one would handle the NoFound
directly, even running more complex code or using information not passed
to the lookup, as showed here:

```
// local_var is declared up here.
try {
  function_that_calls_lookup(args);
} catch NoFound(key) {
  if (is_special_key(key)) {
    resume build_default_value(key, local_var);
  } else {
    resume simple_default_value;
  }
}
```

### Void Resume/Terminate Example (Out of Memory)
The third type of resumption is a void return, which you may resume but do
not pass any data back to the throw.

```
try {
  ...
} catch NoMem(size_t target_size, Buffer * buff) {
  if (void * new_buff = realloc(buff->data, target_size)) {
    buff->data = new_buff;
    resume;
  }
}
```

Here, the error cannot be fixed with information, but rather a correction to
the container state. This also shows how resumption is optional (for both
void and value throws) and the handler can instead decide to terminate and
unwind the stack.

### Polymorphism Example (Save/Restore)
Higher order functions can handle types simply need to polymorphic on the
same exception type as the functions they take as parameters.
```
forall(T, exception E)
T isolate_state(T (*func)() throws (E), State state) {
  // SavedState wraps a State and restores it on deconstruction.
  SavedState _saved = save_state();
  set_state(state);
  return func();
}
```
This isolates a function by saving and restoring state around the function,
where that state is some generic global state. The wrapped function can raise
any of those exceptions and they will pass through the isolateState. When
one of those exceptions terminate or func returns, _saved's destructor runs.

### Polymorphic Error Example (foreach)
Here is a simple example of polymorphism over a higher order function.

```
void print_widget(Widget &) throws (noreturn FormatError());

forall(T, [N], exception E) throws(E)
void foreach(void op(T &) throws(E), array(T, N) & data) {
  for (i; N) op(data[i]);
}

forall([N])
void print_widgets(array(Widget, N) & widgets) {
  // FormatError not handled.
  foreach(print_widget, widgets);
}
```
It is infact too simple as foreach will have the same exception signature
as print_widget and that means print_widgets is not handling the function
nor passing it to its caller.

However, this example (or a version that catches FormatError) will work
because the exception signatures all match.
```
forall([N])
void print_widgets(array(Wiget, N) & widgets) throws (noreturn FormatError()) {
  foreach(print_widget, widgets);
}
```

Details And Additional Notes
----------------------------

### Rethrows and Conditional Catch
As extension to the core system, we could add rethrowing or conditional
catching to try blocks. There purpose is the same, to allow a handler to
use non-type information to decide if it can handle an exception or not.

If an exception is rethrown or the condition evaluates to false, exception
propogation continues up the stack. As far as control flow goes this should
be the same as not catching it at all.

A rethrowing or conditional catch does not remove the exception it could
catch and handle from the unhandled exception set. Because it may not, so
the type system has to account for that.

### Performance (Where to Pay Costs)
Although the behaviour has been laid out, the performance has not. We would
like "good" performance of course, but there is a choice between fast tries
and fast throws.

The current system uses fast tries, which means it is designed for very rare
cases, almost always errors.

Fast throws mean that entering and leaving a try block is now more expensive,
but it means throws can be common. Exceptions can now be used for alternate
returns and other relatively frequent cases.

A single choice doesn't have to be made for the entire language. It could
choose differently for different cases or even let the user decide for
different exceptions.

### Implementation
The implementation has not been specified. See the performance section for
fast tries vs fast throws.

The current implementation could be adapted if we want to stay with fast
tries. The exception could become an id and a pointer to stack allocated
storage. If the id can even be contextual and if a function doesn't handle
the exception, it gives a new id to pass onto its owner.

(You might need to modify libunwind to allow returning successfully from an
unwind call without unwinding, but last time I checked that change could be
made in a platform independent part of libunwind.)

### Polymorphic Tracing Details
For simplicity, polymorphic exceptions should probably just be black boxed
and not interact with any other handlers or exceptions except at the edges
where it is boxed and unboxed.

The programming language Flix has effect polymorphism (but not polymorphic
effects, avoid confusing those), which has many of the same design
constraints.

### Polymorphism Ease of Use
It would be nice if the language's defaults were set up in such a way that
the common usage pattern is the simplest/most natural way to write a
function.

For a point of comparison, consider otype (object-type) parameter `(T)` as
compared to the dtype (data-type) parameter `(T &)`. Although dtype is
the more primitive/simplest form, the simplest syntax goes to otype, which is
the most common form. Something similar could be done for higher-order
polymorphic functions.

Something like if a polymorphic function takes another function and none of
them have explicit exception signatures, a exception polymorphic argument
could be added and all the functions may throw that. Details may depend on
how widening works. Also, you must be able to explicitly say that functions
do not throw, doing this to one of the functions (by convention, the main
function, not any function parameters) would disable this feature.

The exact rules should come from usage patterns that emerge once the feature
is implemented.

### Unwind Timing
Although the current implementation can mimic both termination and resumption
exceptions. There is one major difference in the behaviour of the termination
throw and catch, the unwinding of the stack now occurs after running the
handler instead of before.

When the handler is short and operates locally, this is unlikely to matter.
In fact, this can fix some life-time issues (see implementation).
However, if there is a lot of work in the handler we haven't cleared the
stack up or freed up other resources (the destructors haven't run, things
are still taking up stake space, etc.).

If this turns out to be a problem, there should either be a way to force an
unwind in the handler, or make sure local control flow can take us to an
external block to handle the exception.

### Exceptions in Traits
In another way to make exceptions like assertions, they could be added into
traits. They can probably just mixed into the bodies with assertions.

This would generally just be a short-hand, but also offers a tool to
organize exceptions like in effects, which can have multiple options.

### Cancellation
This system is narrower than the current exception system. Rather than trying
to cover every use case directly/with one feature, I would propose another
feature to handle some of the important cases not seen covered here.

The biggest other cases are the non-recoverable cases. These may not be
caught are all (in which case they are the same as an abort) or are caught
and the operation is abandoned. Control then continues from a main loop
(as in an engine or server dispatcher).

For these cases, an unchecked exception that only supports catch-all or our
cancellations might work better. They only information they need to carry is
enough for a user facing error message.

### Default Exception Handlers
Handling every exception has often proven more work than people want to put
in. Now, this may not be an issue with fewer more significant exceptions,
but if it is default exception handling within a program might help.

This could be a universal thing, such as triggering an abort or cancellation
if an exception is not handled, or something narrower using assertions or
configuration on the exception itself.
