The protocol class that corresponds to an encapsulating stream. If you want to create a new class that behaves like an encapsulating stream, it should be a subclass of encapsulating-stream. All instantiable subclasses of encapsulating-stream must obey the encapsulating stream protocol. Members of this class are mutable. [annotate] |
Returns true if object is an encapsulating stream, otherwise returns false. [annotate] |
All encapsulating streams must handle the :stream initarg, which is used to specify the stream to be encapsulated. [annotate] |
This instantiable class provides a standard implementation of an encapsulating stream. [annotate] |
The standard-encapsulating-stream class must provide "trampoline" methods for all stream protocol operations. These "trampolines" will simply call the same generic function on the encapsulated stream. In particular, all of the generic functions in the following protocols must have trampolines. [annotate]
The following generic function must also be implemented for all encapsulating stream classes. [annotate]
Returns the stream encapsulated by the encapsulating stream encapsulating-stream. [annotate] |
The suggested implementation of encapsulating streams has a potential problem that we label the "delegation" or "multiple self" problem. Here is an example of the problem. [annotate]
Suppose we implement accepting-values by using an encapsulating stream class called accepting-values-stream that will be used to close over an ordinary extended input and output stream. Let us examine two generic functions, stream-accept and prompt-for-accept. The stream-accept method on an ordinary stream calls prompt-for-accept. Now suppose that accepting-values-stream specializes prompt-for-accept. If we now create a stream of type accepting-values-stream (which we will designate A) which encapsulates an ordinary stream S, and then call stream-accept on the stream E, it will trampoline to stream-accept on the stream S. The desired behavior is for stream-accept to call the prompt-for-accept method on the stream E, but instead what happens is that the prompt-for-accept method on the stream S is called. [annotate]
In order to side-step this problem without attempting to solve a difficult general problem in object-oriented programming, CLIM implementations may introduce a special variable, *original-stream*, which is bound by trampoline functions to the original encapsulating stream. Therefore, the stream-accept on the ordinary stream S will call prompt-for-accept on the value of (or *original-stream* stream). This idiom only needs to be used in places where one stream protocol function calls a second stream protocol function that some encapsulating stream specializes. [annotate]
This scheme has two rather undesirable patterns:
a) In core, all stream methods which invoke other stream methods must check *original-stream* and prefer that. (Because a priori they won't know whether they will be involved in such a situations).
b) User code reaches into core class S and (b.1) defines (<stream func> :around (stream S)), or even (b.2) replaces the primary specialized on S, so that it checks *original-stream*.
(a) is not good, but (b) is worse. (b.2) is of course unworkable. If S already has an :around then (b.1) dies. If multiple wrapper classes exist then (b.1) might again die, depending on the exact changes the wrappers make. If a delegated class tries to enclose a delegated class then (a) and (b) die. If the stream invokes a continuation on another thread then (a) and (b) die because specials are probably thread-local. I'm sure we can come up with other issues.
Would it not be simpler to have two classes "accepting-values-input-stream" and "accepting-values-output-stream", and these are what accepting-values-stream closes over? It would be accepting-values-input-stream which would specialize on prompt-for-accept (using its own class, not S), and do the proper things with its enclosing and sibling streams.
Of course, the delegating stream may need to deal with enclosed streams specified at run time; this would require creating classes dynamically.
[edit]-- Michael South 2024-09-05 23:54ZThis "solution" does not solve the more general problem of multiple levels of encapsulation, but the complete stream protocol provided by CLIM should allow implementors to avoid using nested encapsulating streams. [annotate]
This variable is bound by the trampoline methods on encapsulating streams to the encapsulating stream, before the operation is delegated to the underlying, encapsulated stream. [annotate] |