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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.