This module contains utilities to control execution flow.
Contexts are very similar to Python's
with
statement
and can be seen as a more generic version of Java's
try with resource
A context is any object exposing two special methods.
The first one, __$$_enter
, is a parameterless method that must be called when entering the context.
The second one, __$$_exit
, is a method that must be called when exiting the context.
A context can be created using the provided Context
structure, a DynamicObject
, or by augmenting existing
Java objects (e.g. CloseableContext
)
A context is meant to be used with the within
macro, that wraps the execution of its block with these
enter and exit methods. This allows to abstract try...catch...finally
patterns.
Indeed, the given block is wrapped inside a try...catch...finally
to ensure that the __$$_exit
method of the context is called.
The result of the __$$_enter
method of the context can be bound to the given name to be used inside the block.
For instance, given:
&within(name = expression) {
block
}
The expression
is evaluated. It is expected to return a context.
The context __$$_enter
method is invoked without arguments.
Its result (the target) is bound to name
if present, and is ignored otherwise.
The block
is executed (in a try
clause).
Depending on the exception thrown by block
:
block
raised an exception, the __$$_exit
method is called with the target and this exception.
If the method returns an exception, it is thrown.
Must the exception raised by the block
be rethrown, the __$$_exit
must return it.
If the __$$_exit
returns null
, no exception will be raised.
Any exception raised by the __$$_exit
method is
suppressed.block
does not raise an exception, the __$$_exit
method is called with the target and null
and its returned value is ignored.
Any exception raised by the __$$_exit
method is rethrown.Since the result of the __$$_enter
method is given to the __$$_exit
method, the context is not required to keep it as an attribute, should __$$_exit
act on it.
The context can therefore be stateless. It may however keep the target as an internal attribute, or a closed variable when using Context
for instance.
Since the __$$_exit
method is given the exception raised by the block and can prevent its rethrow, the exception can be dealt with directly inside this method.
Moreover, since the exception returned by __$$_exit
is raised, the exception can be wrapped in a higher level exception.
For instance, one can create a transactional context as:
function transaction = |params...| -> context(
-> createConnection(params),
|connection, err| {
case {
when err is null {
connection: commit()
}
when err oftype MyException.class {
connection: rollback()
dealWithIt(err)
}
otherwise {
connection: rollback()
return WrapperException("Transaction failed", err)
}
}
}
)
...
&within(connection = transaction(connectionParams)) {
doSomethingWith(connection)
}
More than one context can be used, which is the same as nesting contexts, as:
&within(x = context, generateContext()) {
work(x)
}
is expanded into
&within(x = context) {
&within(generateContext()) {
work(x)
}
}
This structure encapsulate two functions that will be used as context and enter and exit functions.
See also the applied augmentation to provide the corresponding context methods.
This structure must not be instanciated directly, use context
instead.
enter
A closure executed when entering the context.
This closure takes no argument and return the value that will be bound to the context variable.
Can also be a value (e.g. null
if no action is to be executed on entry).
exit
A closure executed when exiting the context.
This closure has two parameters: the target (as returned by enter
) and the exception raised by the wrapped block.
Can also be a value.
Augmentation to create a context from objects with a close
method.
Classes with a close()
method can be augmented with this augmentation to behave as a context.
The enter value is the object itself, and the exit method call its close
method and returns the exception unchanged.
This allows a behavior similar to the Java try with resource.
See also closing
to use a wrapping function instead.
AutoCloseable
are augmented using this augmentation.
Augmentation to create a context from objects with lock()
and unlock()
methods.
Classes with lock()
and unlock()
methods can be augmented with this augmentation to behave as a context.
The enter value is the object itself, after it was locked, and the exit method unlocks it and returns the exception
unchanged.
See also locking
Makes the Context
structure a context by providing __$$_enter
and __$$_exit
methods that delegate on
the corresponding closures.
In both cases, if the field value is not a closure, it is used as the method's return value.
Delegates on the enter
field.
Delegates on the exit
field.
Java AutoCloseable
objects are augmented to be a closing context.
For instance, to read the lines of a file, one can use:
&within(f=openFile("somefile.txt")) {
foreach line in f {
println(line)
}
}
See also closing
.
Java Locks
objects are augmented to be a locking context.
For instance:
let lock = ReentrantLock()
&within(lock) {
doSomeWork()
}
See also locking
Execute a block within a context.
This macro must be called with at least a context and a block to execute.
The contexts may be given a name that will be bound to the result of __$$_enter
.
Several context may be given, which is equivalent to nested contexts.
For instance:
&within(x = context1, generateContext(), y = otheContext()) {
work(x, y)
}
is expanded into
&within(x = context) {
&within(generateContext()) {
&within(y = otherContext()) {
work(x, y)
}
}
}
The __$$_exit
method of each context will be called accordingly, even if the block fails with an exception.
Creates a context for resources that must be closed.
For instance:
&within(resource=closing(createCloseable())) {
doSomethingWith(resource)
}
resource
: the resource to close on exit. The only constraints is that the resource must have a close()
method. exception unchanged
See also CloseableContext
to augment existing classes instead.
Generic context to deal with exceptions.
The created context returns null
on entry. The exit function ignores the target, and apply the given mapper
function
to the exception.
For instance, to just log a message and ignore the exception, one can use:
let errorLog = exceptionFilter(|e| { Messages.error(e: localizedMessage()) })
&within(errorLog) {
somethingThatMayRaise()
}
mapper
: an unary function whose parameter is an exception (may be null
) and returns an exception to raise
or null
if no exception must be raised.Convenient function to create a locking
context from a java.util.concurrent.locks.ReentrantLock
.
Creates a context for locking objects.
For instance:
&within(locking(createLock()) {
doSomething()
}
lock
: any object with lock()
and unlock()
methods.lock()
method (and returns it) and the exit method
unlocks it using its unlock()
method and returns the exception unchanged.See also LockContext
to augment existing classes instead.
Creates a null context.
This null context does nothing on exit, and return the given value on entry. It can be used as a fallback value when the context to use is changed dynamically.
It's a Null Object Pattern instance.
This function should be banged.
val
: the value to assign on entry.Creates a context redirecting standard error.
err
: a java.io.PrintStream
that will be used as standard error.Creates a context redirecting standard output.
out
: a java.io.PrintStream
that will be used as standard output.Creates a context that ignores exceptions.
exceptions
: the exceptions to ignoreCreates a context for unlocking objects.
This context can be used to temporally release a previously acquired lock.
For instance:
let l = createLock()
&within(l) {
doSometing()
&within(unlocking(l)) {
doTaskWithLockReleased()
}
workWithLockHeld()
}
lock
: an object with lock()
and unlock()
methods.unlock()
method (and returns it) and the exit method
locks it again using its lock()
method and returns the exception unchanged.Creates a context that wraps exceptions
If an exception is raised inside the context, it will be wrapped in the given exception (as its cause).
For instance, given:
&within(wrapped(MyException.class)) {
doSomething()
}
If doSomething
throws an exception e
, a MyException
instance will be raised instead, whose cause will be set to
e
.