Previous ... Next

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.

      Previous ... Next