[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Law of good style for CLOS

All the feedback we got on the Law of good style for CLOS has
prompted the Demeter team to find the proper generalization
of our Flavors/Smalltalk/C++ Law to the CLOS Law.
We give here our solution and explain it on some of the examples 
which you sent us. A pretty complete explanation will be in our
OOPSLA '88 paper. A copy will be sent to Daniel Bobrow (as he
----- LAW OF DEMETER (CLOS, object version) -------------
All function calls inside a method M must have
one of the following method selection argument objects:
  - M's argument objects or
  - slot values of method selection argument classes of M.
(Objects created by the method and objects in global 
variables are viewed as being passed by arguments.
A method selection argument is an argument which is used
for identifying the applicable methods.)
This formulation is a natural generalization of the Flavors/
Smalltalk/C++ Law where there is exactly one method selection
argument (the first one). Note that there is no restriction on
non-generic function calls: they have 0 method selection arguments.
The Law requires the programmer to be aware whether a function 
is generic (reversing our earlier point of view in our reply
to Scott Fahlmann's message).

Richard Gabriel invented the following example: 

>(defmethod hack-positions  ((p1 position) (p2 position))
>  (let ((p1-x (x-coord p1))
>	(p1-y (y-coord p1))
>	(p2-x (x-coord p2))
>	(p2-y (y-coord p2)))
>    (let ((rho1 (sqrt (+ (* p1-x p1-x) (* p1-y p1-y))))
>	  (theta1 (atan p1-y p1-x))
>	  (rho2 (sqrt (+ (* p2-x p2-x) (* p2-y p2-y))))
>	  (theta2 (atan p2-y p2-x)))
>      (let* ((q (f rho2 theta2 rho1 theta1))
>	     (r (g q))
>	     (s (h r))
>	     (tt (i s))
>	     (u (j tt)))
>        (k u)))))

>Is the call (x-coord p1) a violation?  [It is not known whether it is a
>slot access or a function call.] Is the call (x-coord p2) a violation?
>[It is invoked on something besides the first argument to the method.]  

NO VIOLATION since p1 and p2 are method selection arguments.

>Is the call to SQRT a violation?  [Its first argument is a number, but is
>not related to the first argument to the method.]

NO VIOLATION since + returns a new object.

How about the call to ATAN?
>[Its first argument is related to the second argument to the method
>via a LET variable.]

A VIOLATION if we assume that the objects returned by (x-coord p1)
and (y-coord p1) are not parts of p1. Otherwise no violation.

>How about the call to f?  [Its first argument is
>derived from something related to the second argument to the method.]  

f is in GOOD STYLE since it gets all newly created objects.

>The call to g?  [Its first argument is indirectly related to the arguments to
>the method.

There is not enough type information here to tell.

>Karl, if you could answer whether anything in HACK-POSITIONS violates the law
>and why, we could begin to think about the issue you're raising. If nothing
>here violates the law, could *you* provide a method that violates the law?

Here is a simple example.
;;; A = B. 
(defclass A () ((B :type B)))
;;; B = C.
(defclass B () ((C :type C)))
;;; C = D.
(defclass C () ((D :type D))) 

(defmethod violation ((p1 A) (p2 Z))
  (slot-value (slot-value (slot-value p1 'b) 'c)))

The idea is that it is a bad strategy to put a dependency on the
C class into this method which is "attached to" A and Z.
Jim Kempf's remark promotes further encapsulation. It enhances the
Law but does not replace it.

>If a class is defined with accessor generic functions whose names are
>exported from the package, but are different from the slot names,
>which are not exported (or "hidden"), then the only access to the
>slots is through a functional interface. From the client's point of
>view, invoking a generic function on the object need not have any
>relation to slot access at all, much less an inherited or noninherited
>slot. This is an even stronger form of encapsulation, since the structure
>of the class is now completely hidden. Of course, an inheriting client
>may still want access to that structure.

I fully agree. It is good that CLOS allows to generate such an interface.
For completeness I include the Smalltalk-80 version:

----- LAW OF DEMETER (Smalltalk-80, object version) ----
In all message expressions inside a method M 
the receiver must be one of the following objects:
  - an argument object of M including objects in 
      pseudo variables self and super or
  - an instance variable object of the class to which M
      is attached.
(Objects created by the method and objects in global 
variables are viewed as being passed by arguments.)
I am very interested in your feedback regarding the understandability
of our formulation of the Law of good style.

-- Karl