Re: To CLOS or not to CLOS?
Pascal Costanza <[email protected]>
| Newsgroups | gmane.comp.lang.lightweight |
|---|---|
| Message-ID | <[email protected]> |
On 20 Feb 2007, at 23:40, Len Charest wrote:
> Pascal Costanza wrote:
>> - It doesn't provide strong encapsulation: Methods are defined
>> outside classes (to make multiple dispatch work and, more
>> specifically, to integrate method dispatch better with functional
>> programming). So if a method needs access to some state that an
>> object provides, you need to provide that state to that method.
>
> <delurk/>
>
> I don't follow this. If a method needs access to some state that an
> object provides, then the object should be one of the arguments to the
> method. Provide the necessary state by providing an object which
> encapsulates the state. How is the situation here wrt CLOS any
> different
> than it is in single dispatch OO languages? Is it merely that in C++
> (for example) "this" gives you the necessary state implicitly?
Here is what I mean by example. In Java, you can encapsulate fields
in objects and let methods do the work on them:
class Foo {
private int bar = 42;
public void baz(...) {
... do secretly something with bar ...
}
}
No one else can access the bar field in Foo objects except for
methods inside the class Foo.
In CLOS, methods are defined outside classes, so you have to make
sure that classes expose their state to such methods:
(defclass foo ()
((bar :initarg :bar :accessor bar)))
(defmethod baz ((obj foo)...)
... do something with (bar obj) ...)
In general, everyone can call the bar accessor, or even access the
bar slot directly with (slot-value some-foo-object 'bar). (See below,
though.)
However, I don't think that access control should be tied to class /
object definitions because this puts too much burden on these
mechanisms and can have unpleasant side effects. It should better be
part of some module system. For example, in Oberon you also define
methods outside classes, but you can encapsulate definitions inside
modules:
MODULE MyModule;
TYPE Foo* = RECORD bar: INTEGER END;
PROCEDURE (obj: Foo) Baz* (...)
BEGIN ... do secretly something with obj.bar
END Baz;
END MyModule.
[Note that all identifiers marked with an asterisk are exported from
an Oberon module - the other ones are kept private. The Baz procedure
is an example for a "type-bound procedure" in Oberon-2, here for the
type Foo, which is the same as a method in OOP terminology.]
Now, in Common Lisp, you get actually quite close to that with
packages which allow you to hide symbols (identifiers) from other
packages:
(defpackage :mypackage
(:export foo baz))
(in-package :mypackage)
(defclass foo ()
((bar :initarg :bar :accessor bar)))
(defmethod baz ((obj foo) ...)
... do semi-secretly something with (bar obj) ...)
Here, the identifiers / symbols 'foo and 'baz are exported, but 'bar
is not, so clients of this package cannot access it.
Note, however, that Common Lisp doesn't provide strong encapsulation,
but only information hiding: It is possible to circumvent package
boundaries by telling Common Lisp that you _really_ need access to
some symbol although it isn't exported by its package, here for
example by calling mypackage::bar. (The two colons indicate that you
are breaking abstraction boundaries here.)
This is basically an engineering trade off: The designer of a library
might have guessed wrong what should and should not be exported, so
you may need to override that decision to get your product out the door.
Pascal
--
Pascal Costanza, mailto:[email protected], http://p-cos.net
Vrije Universiteit Brussel, Programming Technology Lab
Pleinlaan 2, B-1050 Brussel, Belgium