Exceptions and their handling in Libretto are considered here. Please pay your attention to local traps.
The invoked
Libretto supports two types of traps:
Try expressions are familiar to many programmers. Local traps work in paths. They provide a lightweight handling, when exceptions are processed locally.
A
Here is an example of a binary
If a
A local exception trap is an expression of the form
The local trap can hunt only for exceptions thrown from the nearest expression:
Immutable structures are good as exception objects, because they are powered by pattern matching:
The following trap catches all exceptions:
Local traps are helpful for work in the EAFP style (“it is Easier to Ask for Forgiveness than for Permission”), which prefers the control via exceptions over the control via
- How to Throw an Exception
- Operator try and Global Exception Handling
- Local Exception Handling
- The Empty Context as the Weakest Exception
- Deep Backtracking with jump
How to Throw an Exception
Theerror
function is used for throwing exceptions. It can be called with one or two arguments:
error(exc: Any) error(exc: Any, value: Any)The first argument is the object of the thrown exception. Any value can play the role of this object. The optional second argument serves as the context value for the exception handler. If the second argument is omitted, then the same context value is used for the handler as for the expression, in which the exception occurs.
The invoked
error
function stops the computation of the current expression and hands over the control to the nearest exception trap, whose condition matches the exception object (the first argument of error
).
Libretto supports two types of traps:
- the
try
operator intended for exception handling in large code segments; - a local trap intended for on-the-spot exception handling.
Try expressions are familiar to many programmers. Local traps work in paths. They provide a lightweight handling, when exceptions are processed locally.
Operator try and Global Exception Handling
The behavior of thetry
operator in Libretto is like that in Java and Scala:
(1, -1, 2, 3). try { if ($ < 0) error('negative) $ * 1000 } catch {case 'negative => -$} finally {$ + 1} // 1001 2 2001 3001A
try
expression has the form:
try {try-block} catch {catch-block} finally {finally-block}The ‘catch’ and ‘finally’ blocks are optional. The
catch-block
has the form of a case expression (a partial function).
A
try
expression has the following semantics:- It is executed for each element of the context sequence.
- First the
try-block
is executed for the current context value. - If an exception is thrown, then the
catch-block
is executed, which tries to catch and handle the exception. - If the condition of some
case
expression successfully matches with the exception object, then the right-hand side of thiscase
expression is executed in the context of the second argument oferror
(if it is binary), or in the same context as for thetry-block
(if error is unary). Its result is interpreted as the result of thecatch-block
. - If a
try
expression contains afinally-block
, then it is executed in the context of either the result of thetry-block
(if it has been finished), or thecatch-block
(if an exception has been thrown). The result of the wholetry
expression for the current context value is the result of thefinally-block
. - If there is no
finally-block
, then the value of thetry
expression for the current context value is the value of thetry-block
(if its computation has been successful), or thecatch-block
(if an exception has been thrown). - If a
catch-block
does not manage to catch an exception object, then the exception is thrown out. - In paths, a
try
expression is part of a step.
Here is an example of a binary
error
application:
(1,2,0,4). try {if ($ == 0) error(“div by 0”, “oo”) else 100 div $} catch {case “div by 0” => $ + “ps”} // 100 50 “oops” 25Unary
error
is expressible in terms of binary one:
expr. try {... error(e) ...} ...is equivalent to
expr as x. try {... error(e, x) ...} ...
Finally-block
s in nested try
expressions are computed in the order they occur. If an exception is thrown, the most nested matching catch
is executed. The catch-block
and finally-block
are optional and can be omitted (but not both).
If a
try
expression occurs in a path step, then it must be enclosed in parentheses, except if it is the rightmost in a path. Then the parentheses can be omitted (as in the examples above).
Local Exception Handling
The notation oftry
expressions is heavy. Sometimes such heaviness has its reasons (when large code segments are involved, and exceptions are handled globally). But in many cases it is much better to handle exceptions locally on the spot. Such situations are quite frequent (especially when duck-typing is involved), therefore local exception-handling tools are added in Libretto.
A local exception trap is an expression of the form
expr ?{partial-function}This local trap is sensitive to the expression
expr
on the left-hand side of the question mark ?
. Only exceptions thrown from this expression are caught by this trap. The next function divides 100 by the context value:
def div100 = if (this == 0) error(“div by 0”, “oops”) else 100 div thisIf the context value is
0
, div100
throws an exception:
(1,2,0).div100 // ERROR: div by 0Since the exception is never caught, it stops the computation. Let us revise the expression:
(1,2,0). div100 ?{case “div by 0” => ($, 0)} // 100 50 “oops” 0A local trap behaves as follows:
- When an exception is thrown, the local trap tries to match the exception object against its case patterns.
- If matching is successful for some
case
pattern, then the handler in the right-hand side of thiscase
pattern is computed with the context value determined by the second argument of the functionerror
. - If the second argument of
error
is omitted, then the computation is performed in the same context as for the expressionexpr
, in which the exception occurs. - The value of the whole expression is equal to the value of the handler.
- If the exception object can not be caught, the exception is thrown out.
The local trap can hunt only for exceptions thrown from the nearest expression:
(1,2,0). div100. ($ + 5) ?{case “div by 0” => ($, 0)} // ERROR: div by 0Here the local trap is tuned on the expression
($ + 5)
, but the problem occurs in div100
. This exception cannot be caught. In such cases we can use the try
operator, or put the expression in parentheses or curly brackets:
(1,2,0).(div100.($ + 5)) ?{case “div by 0” => ($, 0)} // 105 55 “oops” 0A trap set after a block operator can catch exceptions thrown from any expression of this block:
{var x = (1,2,0).div100; x = x + 5} ?{case “div by 0” => $} // “oops”Here the trap has caught an exception from the first assignment of the block.
Immutable structures are good as exception objects, because they are powered by pattern matching:
fix class ColorErr(msg: String) fix class ValErr(msg: String) class Color(clr: Any) { fix color: String clr match { case c if c == (“red”, “green”, “blue”) => color = c case x if String => error(ColorErr(x)) case x => error(ValErr(x)) } } def createColor(x) = (Color(x).color) ?{case ColorErr(x) => x + “!”; case ValErr(x) => x +“?”} createColor(“red”) // “red” createColor(“Ann”) // “Ann!” createColor(123) // “123?”Since exception handlers are partial functions, regular pattern matching works in them.
The following trap catches all exceptions:
expr?{case _ => handling any exception}Please remember that disabling all exceptions at once is not a good programming style.
Local traps are helpful for work in the EAFP style (“it is Easier to Ask for Forgiveness than for Permission”), which prefers the control via exceptions over the control via
if
checks. This approach is closely related to duck typing and is a popular programming style in Python. For instance, the expression using the if
operator for checks
var ham = spam. if (hasField('eggs)) eggs else handleError()in the EAFP style looks as follows:
var ham = spam.eggs ?{case “no field” => handleError()}The
hasField
function checks if a certain field is defined on the context object.
The Empty Context as the Weakest Exception
Sometimes we need to prevent backtracking caused by the empty sequence in paths. For instance, the expression(1, -1, 2)?[$ > 0] // 1 2returns
()
for the value -1
, because the predicate does not let it through. The empty sequence launches backtracking and selecting the next context value (2
in this case). A special trap expr?(...)
, which catches empty sequences, allows us to avoid such situations:
(1, -1, 2) as x ?[$ > 0] ?(-x * 100) // 1 100 2The value in parentheses substitutes the empty context.
Deep Backtracking with jump
Theerror
function has a twin function jump
. Its behavior is almost similar to that of error
(with the exception of transactions, see section Multitasking), but the use of jump
indicates that the normal exit is performed, rather than an error occurs. Consider the following definitions:
fix class Person(name: String, surname: String, hasChild: Person*) def Person findDescendant(name: String) { fix ch = hasChild?[name == @name] if (ch) ch else hasChild.findDescendant(name) } object JOHN extends Person(“John”, “Smith”, Person(“Paul”, “Smith”, Person(“Ann”, “Smith”)), Person(“Ann”, “Thomas”)) JOHN.findDescendant(“Ann”).surname // “Smith” “Thomas”The
findDescendant
function enumerates person’s descendants and selects those of them who have a specified name. But if we need to check whether there exists such a descendant, the complete enumeration is needless, because it is sufficient to find the first of them. By using jump
this problem can be solved:
def findDescendant(name: String) { fix ch = hasChild ?[@name == name] if (ch) jump(ch) else hasChild.findDescendant(name) } JOHN.findDescendant(“Ann”) ?{case ch if Person => ch.surname} // “Smith”The first found Ann induces the jump, which throws the object of Ann as the result. In the caller this object is caught. If no offspring found, the result is the empty sequence.
No comments:
Post a Comment