with-contexts
2024-10-12
The WITH-CONTEXT System. A system providing a WITH macro and 'context'ualized objects handled by a ENTER/HANDLE/EXIT protocol in the spirit of Python's WITH macro. Only better, or, at a minimum different, of course.
WITH-CONTEXTS
Copyright (c) 2020-2024 Marco Antoniotti See file COPYING for licensing information
DESCRIPTION
This library contains an implementation of a WITH
macro with
"contexts" inspired by Python, which, in turn was obviously inspired
by Common Lisp macros (and other earlier languages, like Pascal).
The Python library is described in the documentation of the
contextlib
documentation. The current library is an implementation
that overlaps with the Python one, as a few things are available in
Common Lisp that are not available in Python and two things are
present in Python, that are not available in Common Lisp: the yield
statement and built-in threading for asynchronous computations. Note
that the yield
statement could be built in Common Lisp using a
delimited continuation library like cl-cont
. The asynchronous
extensions could instead be directly built on top of the current
library.
Most of the Python examples described in the ... context of contextlib
are directly translatable into Common Lisp using the present library.
The main difference is that, in order to leverage the Common Lisp
Condition subsystem the "protocol" that "contexts" must implement is
comprised of three generic functions:
ENTER <context>
HANDLE <context> <condition>
EXIT context
The WITH macro is practically expanded as follows.
(with [VAR =] CONTEXT-ITEM do CODE)
becomes
(let ((VAR NIL))
(unwind-protect
(progn
(setf VAR (ENTER CONTEXT-ITEM))
(handler-case
CODE
(error (e)
(HANDLE VAR e))
))
(EXIT VAR)))
With this setup, WITH-OPEN-FILE
can be immediately rewritten as
(with f = (open "some.txt") do
(loop for line = (read-line f)
while line
do (do-stuff-to line)))
provided that the proper ENTER
/HANDLE
/EXIT
protocol is in place.
That is, something like the following.
(defmethod enter ((s file-stream) &key)
(if (open-stream-p s)
s
(error "Stream ~S is not open." context-item)))
(defmethod handle ((s file-stream) (e error) &key)
(call-next-method))
(defmethod exit ((s file-stream) &key)
(when (open-stream-p s)
(close s)))
Note that in Python, HANDLE
does not exist and EXIT
is called close
.
More Elaborated Contexts
The contextlib
Python library contains more elaborated "contexts" that
can be used to perform a number of sophisticated operations in
conjunction with the WITH
statement.
EXIT-STACK-CONTEXT
Python introduces ExitStack
as (the following is a direct quote from
Python contextlib
documentation) a context manager that is designed
to make it easy to programmatically combine other context managers and
cleanup functions, especially those that are optional or otherwise
driven by input data.
For example, a set of files may easily be handled in a single with statement as follows:
(with stack = (exit-stack) do
(let* ((files (mapcar (lambda (fname)
(enter-context stack (open fname)))
*filenames*)))
;; Hold on to the new exit stack (not the method pointe as in the
;; Python example), but don't call its UNWIND method
(setf *close-files* (pop-all stack))
;; If opening any file fails, all previously opened files will be
;; closed automatically. If all files are opened successfully,
;; they will remain open even after the with statement ends.
;;
;; (unwind *close-files*)
;;
;; can then be invoked explicitly to close them all.
;; ...
)
Each instance maintains a stack of registered callbacks that are
called in reverse order when the instance is closed (either explicitly
or implicitly at the end of a WITH
statement).
Documentation
Please refer to the full documentation of the with-contexts
library
for more details.
A NOTE ON FORKING
Of course you are free to fork the project subject to the current licensing scheme. However, before you do so, I ask you to consider plain old "cooperation" by asking me to become a developer. It helps keeping the entropy level at an acceptable level.
Enjoy!
Marco Antoniotti 2023-01-21