README.md 4.19 KB
Newer Older
Marco Antoniotti's avatar
Marco Antoniotti committed
1
2
3
WITH-CONTEXTS
=============

Marco Antoniotti's avatar
Marco Antoniotti committed
4
Copyright (c) 2020-2021 Marco Antoniotti
Marco Antoniotti's avatar
Marco Antoniotti committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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.

38
	(with [VAR =] CONTEXT-ITEM do CODE)
Marco Antoniotti's avatar
Marco Antoniotti committed
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

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

55
	(with f = (open "some.txt") do
Marco Antoniotti's avatar
Marco Antoniotti committed
56
57
58
59
60
61
62
63
64
65
	  (loop for line = (read-line f)
	       while line
		   do (do-stuff-to line)))
		   
provided that the proper `ENTER`/`HANDLE`/`EXIT` protocol is in place.
Something like the following.

	(defmethod enter ((s file-stream) &key)
	  (if (open-stream-p s)
	      s
66
          (error "Stream ~S is not open." context-item)))
Marco Antoniotti's avatar
Marco Antoniotti committed
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
		   
	(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:

```
Marco Antoniotti's avatar
Marco Antoniotti committed
98
(with stack = (exit-stack) do
99
  (let* ((files (mapcar (lambda (fname)
Marco Antoniotti's avatar
Marco Antoniotti committed
100
                          (enter-context stack (open fname)))
101
                         *filenames*)))
Marco Antoniotti's avatar
Marco Antoniotti committed
102
103
104
105

    ;; Hold on to the new exit stack (not the method pointe as in the
    ;; Python example), but don't call its UNWIND method

106
    (setf *close-files* (pop-all stack))
Marco Antoniotti's avatar
Marco Antoniotti committed
107
108
109
110
111

    ;; 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.
    ;;
112
    ;;    (unwind *close-files*)
Marco Antoniotti's avatar
Marco Antoniotti committed
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
    ;;
    ;; 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
Marco Antoniotti's avatar
Marco Antoniotti committed
147
20201212