Errors and Exceptions: Messages in AdamsTowel
(1/5)Errors, Messages, Asserts, Guards, .... So much terminology
In 42, when something takes an unexpected turn,
you can throw an «».
This is similar to Java unchecked exceptions.
Every immutable object can be thrown as an error.
While it is possible to thrown informative strings, they do no offer enough
structure to fully take advantage of the error mechanism.
AdamsTowel defines the interface «»:
a structured way to provide a certain kind of message to the user.
There are two main kinds of «»:
«» and «».
While assertions are useful to observe bugs, the application
logic should not depend on them, since they may change
in unpredictable ways during library evolutions, and can be enabled or disabled.
Since program logic can depend on guards being thrown,
guards need to be consistent across library evolution.
Assertions are a convenient tool to prevent the code from proceeding
out of our designed state space. The assertion class called «»
looks like a road sign
and
represents a feeling of "NO/PROHIBITED/FORBIDDEN" or something similar.
Assertions are also very convenient for checking pre/post conditions.
The following code show usages of «» (for preconditions and, in general, blaming the client of a function)
and «» (for postcondition checks in the middle and, in general, blaming the function implementation).
0Num; //simplest form
answer<10000Num msg=S"here with personalized message answer= %answer";
actual=answer, expected=42Num //do a better error reporting
] //in a bunch of assertions, they are all going to be checked/reported together.
recomputedAnswer = 6Num*7Num
X[//postconditions/checks in the middle
actual=recomputedAnswer
expected=42Num
]
X[answer==recomputedAnswer]
if answer>50Num (//how to just throw error X
error X""
)
]]>
As you have seen, we have various ways to check for condition:
«0Num;]]>» checks a boolean condition,
«» checks the condition and uses a custom error message,
«» takes two immutable values
and checks that they are structurally equivalent.
«» is often used as last case in a sequence of if-return;
for example, instead of defining «» inside of «», we could compute it externally as shown below:
As you can see, since there are only 4 directions, we believe by exclusion that the last case must hold. However, we prefer to
make our assumptions clear and have them checked.
(2/5) Create, throw and capture
Create and throw
You can create new kinds of messages using «»
as a decorator:
In 42 interfaces can not have implemented methods, not even class ones, so you may be surprised that we can use
«» as a decorator, since decorating is a method call.
When operators are called on a class name directly, they are desugared as a method on one of its
nested libraries. For example
«» becomes
«».
It is very common for an interface to be usable as a decorator, creating new code with a meaningful default implementation for the interface.
Capturing errors and exceptions
In 42 there is no explicit «» statement,
but any block of code delimited by round or curly brackets can contain «».
In the code example below, lines 2 and 3 are conceptually inside the implicit «» statement.
If nothing is thrown then lines 6, 7 and 8 are executed.
Note that «» and «» can see «» and «»; this would not naturally happen in a language with explicit «» statements; «» and «» would become local bindings inside the «» statement.
The catches above do not see local variables
«» and
«» because they may be capturing an error raised by the execution of the initialization of such variable.
L42 never exposes uninitialized data.
If a catch is successful, then the result of its catch expression
will be the result of the whole code block.
In this way, blocks with catches behave like conditionals.
That is, the code above can assign to
«» either
«»,
«» or
«».
Strong error safety
In 42, error handling guarantees a property called strong error safety
(strong exception safety in the Java/C++ terminology).
This means that the body of a catch must not be able to observe
state mutated by the computation that threw the error.
This is enforced by disallowing catching errors in some situations.
That is, the following code do not compile
While the following is accepted.
As you can see, in the first version of the code,
«» is declared outside of the block
and
«» mutates it.
«» would be visible after the
«» is completed.
In the second version instead,
«» is out of scope after the
«» is completed, and the whole mutable ROG reachable from
«» is ready to be garbage collected.
Intuitively, a programmer who does a bunch of sequential operations on some mutable objects
would expect them all to be executed.
They expect the intermediate states of those objects not to be relevant to the surrounding program. Consider the following example:
Reading this code, most programmers would expect this method to keep the 3 counters aligned.
Exceptions and errors violate this intuition, since they can be raised in the middle of the sequence and prevent the later operations.
For example, if «» fails, then Bob will miss his party, possibly because he is too drunk.
This violates the programmers' expectations
outlined above.
Exceptions can be accounted for, since the type system knows about them; so the programmer can be expected to plan for them.
On the other hand, errors can be raised
anywhere and human programmers often
account for them only as a last resort.
Thanks to strong error safety, this natural attitude of human programmers is somewhat mitigated: while it is true that Bob will miss his party, the program will never observe him in this sorry state. Bob is, indeed, ready to be garbage collected.
Without strong error safety, we could simply catch the error and keep observing Bob in his distress.
(3/5) Exceptions and errors
Exceptions are like checked exceptions in Java.
As with errors, every immutable object can be thrown as an exception.
You can just write «» instead of «» while throwing or catching. When catching, «» is the default, so you can write «»
instead of «».
Exceptions represent expected, documented and reliable behaviour;
they are just another way to express control flow.
They are useful to characterize multiple outcomes of an operation,
where it is important to prevent the programmer from forgetting about
the many possible outcomes while focusing only on their preferred one.
Exceptions are checked, so methods leaking exceptions have to
mention it in their headers, as in the following.
The programmer using
«» has to handle
the possibility that the cancel button was pressed.
However, L42 supports exception inference; to simply propagate the exceptions leaked out of the methods called in a method body, you can write
«», as shown below:
Exceptions do not enforce strong exception safety as errors do,
so they can be used more flexibly, and since they are documented in
the types, we can take their existence in account while writing programs.
Often, the programmer wants to just turn exceptions into errors.
While this can be done manually, L42 offers a convenient syntax: «».
The two snippets of code behave nearly identically:
in the second, the thrown objects are also notified of the
position in the code where they are whoopsed.
This is conceptually similar to the very common
Java patten where checked exceptions are wrapped in unchecked ones.
As we have shown before, we can use
«» to mark branches of code
that the programmer believes will never be executed.
«» implements
«», so code capturing
«» is unreliable: as explained before, AdamsTowel programmers are free
to change when and how assertion violations are detected.
In particular, the programmer may recognize that
such a branch could be actually executed, and thus replace the error with correct behaviour.
Assertions should not be thrown as exceptions, but only as errors.
(4/5) Return
As we have seen, we have used «» to exit
from the closest surrounding pair of curly brackets.
Also curly brackets can have
«» or «», which must complete by throwing a
«»,
«» or
«».
Let's see some examples:
Moreover, curly brackets can be used
to
«» a different result if some computation fails:
Return looks similar to error/exception
Return is actually another thing that can be thrown and captured.
While only immutable values can be thrown as errors/exceptions,
return can throw any kind of value, but returns can not leak
outside of the scope of a method.
Hold your head before it explodes, but curly brackets are just a syntactic sugar
to capture returns; these two snippets of code are equivalent:
Depending on how your brain works,
knowing the mechanics of «»
can help you to use return better and understand why you can omit
«» for simple method bodies, and why you can
write multiple groups of curly brackets and have local returns.
Or it may just be very confusing. If you are in the second group, just
never ever write «» explicitly and continue
on with your 42 experience.
(5/5) Errors, exceptions and return, summary
-
Always detect misbehaviour in your code, and
terminate it with an «».
-
Whenever something outside your
control happens, give it a name and throw it as an error, as in:
It just takes 2 lines, and will make debugging your code so much
easier.
-
Use errors intensively, but use exceptions sparingly:
they are needed only in a few
cases, mostly when designing public libraries.
-
To convert exception into errors, use the convenient short
syntax «».
-
Instead of manually writing long lists of leaked exceptions,
you can use «». This is particularly convenient for small auxiliary methods.
-
It is sometimes possible to write elegant and correct code
that is not covered in layers upon layers of error/exception checking,
but often is not possible or not convenient.
Up to half of good 42 code will be composed of
just error/exception handling, repackaging and lifting.
Do not be scared of turning your code into it's own policemen.