good afternoon,
On 23 Jan 2014, at 6:18 PM, Robert P. Goldman wrote:
James Anderson has asked for a "concise statement of the issue at hand."
thank you for responding to my questions and for shifting the focus of the discussion to how to ensure that deployments of your library continue to work, rather than that they fail - even if evidently rather than nefariously.
i have understood from the ensuing discussion that,
1. the deprecated (2.*) version provided just one implementation for an effective operation - that is, for the behavior of perform given operation and system graph instances.
2. this effective operation incorporated depth-first graph traversal and post-order operator application.
3. that version is to be superseded with one (3.1.0.*) which binds the alternative traversal and application implementations to OPERATION specializations, where each specialization implements a specific combination of traversal and application. (the *-OPERATION classes)
4. the 3.1.0.* version provides no implementation for the OPERATION class.
5. the observed failure mode has been that in systems for which the definitions include an OPERATION specialization, the intended actions do not occur because no dependencies are observed.
6. in order to rectify these failures, it is proposed to reinstate the depth-first+post-order mechanism for the OPERATION class in addition to the alternative mechanisms for specializations of that class.
Faré has had to change the semantics of the OPERATION class. Previously, the OPERATION class would automatically trigger dependencies downward (notably, if you did an OPERATION on a system, then you had to do that OPERATION as well on that OPERATION's children), "leftward," and onto itself ("selfward").
The implementation of this left holes, and also caused problems for extensions (e.g., for TEST-OP, one typically had to define a default PERFORM that did nothing, since testing a system necessarily tested all of the files).
To fix this, Faré had to add new mixin classes, DOWNWARD-OPERATION, UPWARD-OPERATION, SIDEWAY-OPERATION, and SELFWARD-OPERATION.
In order to preserve the integrity of the code, OPERATION remained as the superclass of all operation objects. But its semantics has changed.
if the above is accurate, the change was the methods specialized for OPERATION now do nothing.
This is not a problem for anyone with a "vanilla" ASDF system. The only programmers who are vulnerable to this change are those who have defined their own subclasses of OPERATION (or those who use libraries whose programmers have defined subclasses of OPERATION).
For those cases, I have pushed a change that checks to make sure that all operations that are instantiated are subclasses of either DOWNWARD-OPERATION, UPWARD-OPERATION, SIDEWAY-OPERATION, SELFWARD-OPERATION, or a new NON-PROPAGATING-OPERATION. If this run-time test fails, an error will be signaled and the programmer will have to fix the offending OPERATION subclass's definition.
several issues are under consideration
- how to integrate into PERFORM precedence relations other than "depends on component"? - how to retain the "depends on component" PERFORM protocol for those deployments which require it? - how to provide for future alternative PERFORM protocols?
the situation requires a representation for the graph traversal and operation execution mechanism other than the "depends on component" mechanism which was present in-line in the superseded version 2.* PERFORM and TRAVERSE methods specialized on OPERATION. there are several alternative ways to retain functional integrity despite the protocol changes, some of which have already been considered. the possible realizations manifest a alternative combinations of patterns:
- distinguish those classes which serve as protocol declarations from concrete classes which bind implementations. in the superseded asdf version, OPERATION played both roles. in version 3.1.0.* OPERATION is declared as a protocol class, with implementations bound to the concrete <direction>-OPERATION classes. the vodonosov version would be to ascribe to OPERATION the additional role as a concrete class by reiterating (aspect of) other class definitions for it.
- provide explicit declarations for alternative aspects of the PERFORM protocol and compose them to compute the effective operation. the 3.1.0.* approach is to bind aspects to *-OPERATION specializations and compose them according to class relations in the operations effective method. in this regard, one might also consider decomposing the aspects more explicitly in terms of the representation of the relation according to which the precedence is to be established. one way to express this would be to name the operation specializations according to the component slot which represents the respective relation. this would make it easier to comprehend their composition. another would be to delegate the graph access to other (singleton) instances bound to the operation. this would facilitate future extensions.
among possible approaches:
(defclass operation () ()) (defclass downward-operation (operation) ()) (defmethod apply ((op downward-operation)) (do-x) (do-y) (do-z)) (defmethod require ((op downward-operation)) (propagate-downward))
has been implemented by 3.1.0.*, but compromises deployments of the form
(defclass special-operation (operation) ()) (defmethod apply ((op special-operation)) (do-special))
as no dependency relation is implemented. it has been suggested (the vodonosov version), to reiterate (aspects of) the *-OPERATION methods for OPERATION. to wit (ignoring arguments):
(defclass operation () ()) (defclass downward-operation (operation) ()) (defmethod apply ((op downward-operation)) (do-downward)) (defmethod require ((op downward-operation)) (require-downward)) (defmethod apply ((op operation)) (do-downward)) (defmethod require ((op operation)) (require-downward)) (defun do-downward () (do-x) (do-y) (do-z)) (defun require-downward () (propagate-downward))
this has the benefit, that it could reduce failures among existing deployments. On the other hand, given the dual role which OPERATION would need to play, it is a "encumbered" realization of the "CLASS + SIMPLE-CLASS" protocol pattern, which would impair future extensions as use of call-next-method is constrained.
another alternative, is to realize the inverse, the "ABSTRACT-CLASS + CLASS" pattern , in which OPERATION serves just the role of CLASS and binds a concrete implementation while alternative inherit from ABSTRACT-CLASS.
(defclass abstract-operation () ()) (defclass operation (abstract-operation) ()) (defclass downward-operation (abstract-operation) ()) (defmethod apply ((op downward-operation)) (do-downward)) (defmethod require ((op downward-operation)) (require-downward)) (defmethod apply ((op operation)) (do-downward)) (defmethod require ((op operation)) (require-downward)) (defun do-downward () (do-x) (do-y) (do-z)) (defun require-downward () (propagate-downward))
this would require changes, to move methods from OPERATION to ABSTRACT-OPERATION, but would offer the benefit, that no class which specializes ABSTRACT-OPERATION would necessarily be encumbered by the OPERATION implementation.
as yet further alternatives, it would be possible to implement the combinations of propagation and application by delegating the implementation to a collection of singleton instances. in this case the alternatives could be declared as the default initialization value(s) for the respective (ABSTRACT-)OPERATION subclass. this would no doubt require broader changes to the asdf code base, but would offer the advantage, that the combination could have a degree of independence from the class hierarchy. for the case at hand, it would then be more natural to declare the default behavior for OPERATION than the approach of duplicating the methods of other specializations.
best regards, from berlin,