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.

Upstream URL

gitlab.common-lisp.net/mantoniotti/with-contexts

Author

Marco Antoniotti <mantoniotti@common-lisp.net>

License

BSD
README

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

Dependencies (0)

    Dependents (0)

      • GitHub
      • Quicklisp