From: Jeff
Subject: How to make slots private
Date: 
Message-ID: <177accb.0407271521.31888933@posting.google.com>
I'm trying to learn CLOS so I'm pretty new at this. If there is
documentation/a good tutorial on the subject I'll be more than happy
to read it, but here is the question:

I have a class defined like this:

(defclass bsx-logger ()
  ((stream-name
    :initarg :stream-name
    :initform (error "stream name must be provided")
    :reader stream-name)
   (current-stream)))

running: 

(setf logger (make-instance 'bsx-logger :stream-name "stream.log"))

creates one. So far, so good.

Then I can use

(stream-name logger) => "stream.log"

however, I don't want to allow any of these to work:

(setf (slot-value logger 'stream-name) "new-stream.log")
(setf (slot-value logger 'current-stream) "old-stream.log")
(slot-value logger 'stream-name)
(slot-value logger 'current-stream)

I only want access to go through the generic methods or accessors as
described in the defclass. In this case, I never want stream-name to
be changed and I don't want any kind of exposure to the slot
current-stream.

How do I go about doing this? 

(perhaps poor analogy, but in java I might do this:

public class BsxLogger {
  private String streamName;
  private FileOutputStream currentStream;

  public BsxLogger(String stream_name) { ... }
  public String getStreamName() { return streamName; }
}

so I can't access currentStream from outside this class. This is what
I want in lisp)

Thanks,
Jeff

From: Christophe Rhodes
Subject: Re: How to make slots private
Date: 
Message-ID: <sqacxlui4a.fsf@cam.ac.uk>
······@bigfoot.com (Jeff) writes:

> so I can't access currentStream from outside this class. This is what
> I want in lisp)

Make sure that your slot names are in a package inaccessible from your
application package (except when using the :: package marker).

(defpackage "FOO" (:use "CL"))
(defpackage "FOO-SLOTS" (:use))
(in-package "FOO")

(defclass foo ()
  ((foo-slots::some-slot :accessor some-slot)))

Then (some-slot <foo>) works, but (slot-value <foo> 'some-slot) does
not.  (slot-value <foo> 'foo-slots::some-slot) does, but this can be
made a shootable offence if it's used without good reason.

Christophe
-- 
http://www-jcsu.jesus.cam.ac.uk/~csr21/       +44 1223 510 299/+44 7729 383 757
(set-pprint-dispatch 'number (lambda (s o) (declare (special b)) (format s b)))
(defvar b "~&Just another Lisp hacker~%")    (pprint #36rJesusCollegeCambridge)
From: Jeff
Subject: Re: How to make slots private
Date: 
Message-ID: <177accb.0407290707.3c356015@posting.google.com>
Thank you, your message is very helpful. I implemented your suggestion.
From: Christopher C. Stacy
Subject: Re: How to make slots private
Date: 
Message-ID: <u1xixnedk.fsf@news.dtpq.com>
>>>>> On 27 Jul 2004 16:21:06 -0700, Jeff  ("Jeff") writes:

 Jeff> (defclass bsx-logger ()
 Jeff>   ((stream-name
 Jeff>     :initarg :stream-name
 Jeff>     :initform (error "stream name must be provided")
 Jeff>     :reader stream-name)
 Jeff>    (current-stream)))

 Jeff> I don't want to allow any of these to work:
 Jeff> (setf (slot-value logger 'stream-name) "new-stream.log")

If the user is aware of your implementation (eg. the slot names),
then they can use SLOT-VALUE, whose job is to break any accessor
abstraction.  The API to your program is supposed to be defined
by the symbols that you export from its package.  
Take care not to export the slot names!

Even then, a user could still reference the slots by using
the package-abstraction-breaker: the double-colon syntax.
Supposing your package is called BSX, and you have _not_
exported the symbol STREAM-NAME from it, there's still
no way to stop someone from doing

 (setf (slot-value logger 'bsx::stream-name) "naughty")

but then it's very clear that they are doing it deliberately.
From: Szymon
Subject: Re: How to make slots private - solution (crude one).
Date: 
Message-ID: <87n01k5qrn.fsf_-_@eva.rplacd.net>
······@news.dtpq.com (Christopher C. Stacy) writes:

> ... If the user is aware of your implementation (eg. the slot names),
> then they can use SLOT-VALUE, whose job is to break any accessor
> abstraction. ...

CL-USER> (progn

	   (defclass x () ((s :initform 'FOSHMOO)))
	   
	   (defmethod set-x ((i x) v) (setf (slot-value i 's) v))
	   
	   (defmethod print-x ((i x)) (princ (slot-value i 's)))
		
	   (defparameter *x-instance* (make-instance 'x)))

*X-INSTANCE*


CL-USER> (print-x *x-instance*)
FOSHMOO

CL-USER> (set-x *x-instance* 'bar)
BAR

CL-USER> (print-x *x-instance*)
BAR

CL-USER> (setf (slot-value *x-instance* 's) NIL)
NIL

CL-USER> (print-x *x-instance*)
NIL


;;; --- solution:


CL-USER> (progn

	   (defclass y () ((#1=#:s :initform 'FOSHMOO)))
	   
	   (defmethod set-y ((i y) v) (setf (slot-value i '#1#) v))
	   
	   (defmethod print-y ((i y)) (princ (slot-value i '#1#)))
		
	   (defparameter *y-instance* (make-instance 'y)))

*Y-INSTANCE*


CL-USER> (print-y *y-instance*)
FOSHMOO

CL-USER> (set-y *y-instance* 'bar)
BAR

CL-USER> (print-y *y-instance*)
BAR

CL-USER> (setf (slot-value *y-instance* '#1#) NIL) ; error expected
; Evaluation aborted


;;; any comments?

Regards, Szymon.
From: Christophe Rhodes
Subject: Re: How to make slots private - solution (crude one).
Date: 
Message-ID: <sqvfg8bb2q.fsf@cam.ac.uk>
Szymon <···@bar.baz> writes:

> 	   (defclass y () ((#1=#:s :initform 'FOSHMOO)))
> ;;; any comments?

Consider what happens if you re-evaluate this form (maybe by reloading
a fasl file, or even the source file).

Christophe
-- 
http://www-jcsu.jesus.cam.ac.uk/~csr21/       +44 1223 510 299/+44 7729 383 757
(set-pprint-dispatch 'number (lambda (s o) (declare (special b)) (format s b)))
(defvar b "~&Just another Lisp hacker~%")    (pprint #36rJesusCollegeCambridge)
From: Pascal Costanza
Subject: Re: How to make slots private - solution (crude one).
Date: 
Message-ID: <ce82l4$ld6$1@f1node01.rhrz.uni-bonn.de>
Szymon wrote:

> ;;; any comments?

(use-package :clos) ; or some such

CL-USER 1 > (class-slots (find-class 'y))
(#<STANDARD-EFFECTIVE-SLOT-DEFINITION #:S 214C92AC>)

CL-USER 2 > (first *)
#<STANDARD-EFFECTIVE-SLOT-DEFINITION #:S 214C92AC>

CL-USER 3 > (slot-definition-name *)
#:S

CL-USER 4 > (setf (slot-value *y-instance* *) nil)
NIL

CL-USER 5 > (print-y *y-instance*)
NIL


Pascal ;)

-- 
Pascal Costanza               University of Bonn
···············@web.de        Institute of Computer Science III
http://www.pascalcostanza.de  R�merstr. 164, D-53117 Bonn (Germany)
From: Szymon
Subject: Re: How to make slots private - solution (crude one).
Date: 
Message-ID: <87y8l38dh7.fsf@eva.rplacd.net>
Thanks for the replies.

Regards, Szymon.
From: Larry Clapp
Subject: Re: How to make slots private
Date: 
Message-ID: <slrncge0g9.j7b.larry@theclapp.ddts.net>
In article <···························@posting.google.com>, Jeff wrote:
> I'm trying to learn CLOS so I'm pretty new at this. If there is
> documentation/a good tutorial on the subject I'll be more than happy
> to read it, but here is the question:
> 
> I have a class defined like this:
> 
> (defclass bsx-logger ()
>   ((stream-name
>     :initarg :stream-name
>     :initform (error "stream name must be provided")
>     :reader stream-name)
>    (current-stream)))
> 
> running: 
> 
> (setf logger (make-instance 'bsx-logger :stream-name "stream.log"))
> 
> creates one. So far, so good.
[snip]
> however, I don't want to allow any of these to work:
> 
[snip uses of slot-value]
> 
> I only want access to go through the generic methods or accessors as
> described in the defclass.

If you want them *really* private:

  (defclass bsx-logger () ())
  (defmethod initialize-instance :after ((instance bsx-logger) 
					 &key (stream-name nil stream-name-supplied-p))
    (let (current-stream)
      (if stream-name-supplied-p
	(defmethod stream-name ((instance (eql instance)))
	  stream-name)
	(error "stream name must be provided"))))

This isn't really the Lisp Way, though.  The separate slot-name
package that Christophe Rhodes suggested is much better.  If the
availability of slot-value using that method still bothers you,
remember that different languages solve problems differently.  Java &
C++ say "you may not touch this."  CLOS says "You shouldn't touch
this, but you can if you must."

-- Larry
From: Pascal Costanza
Subject: Re: How to make slots private
Date: 
Message-ID: <ce7eji$dbo$1@newsreader2.netcologne.de>
Jeff wrote:

> however, I don't want to allow any of these to work:
> 
> (setf (slot-value logger 'stream-name) "new-stream.log")
> (setf (slot-value logger 'current-stream) "old-stream.log")
> (slot-value logger 'stream-name)
> (slot-value logger 'current-stream)
> 
> I only want access to go through the generic methods or accessors as
> described in the defclass. In this case, I never want stream-name to
> be changed and I don't want any kind of exposure to the slot
> current-stream.
> 
> How do I go about doing this?

SLOT-VALUE is the low-level facility for accessing slots in CLOS 
classes. Experienced Lisp programmers already know that they shouldn't 
use it light-heartedly. You have declared a reader for your slot - this 
already indicates that you want the reader to be used instead of 
SLOT-VALUE. I recommend not to make it tighter than that.

> (perhaps poor analogy, but in java I might do this:
> 
> public class BsxLogger {
>   private String streamName;
>   private FileOutputStream currentStream;
> 
>   public BsxLogger(String stream_name) { ... }
>   public String getStreamName() { return streamName; }
> }
> 
> so I can't access currentStream from outside this class. This is what
> I want in lisp)

...but this is also not what you want in Java. The recommended practice 
in Java nowadays is to make fields at least protected so that subclasses 
can access them. The idea is that implementors of subclassse know what 
they do when they don't use getters or setters for accessing a field. An 
even stronger argument is the fact that the Java API provides means to 
access even private fields from the outside so that, for example, 
debuggers written in Java can read and set such fields.

Every rule has at least an exception, you know? Don't make it too hard 
to express exceptional cases in your code. Just my 0.02$


Pascal

-- 
Tyler: "How's that working out for you?"
Jack: "Great."
Tyler: "Keep it up, then."
From: Kaz Kylheku
Subject: Re: How to make slots private
Date: 
Message-ID: <cf333042.0407281404.3cdb6b50@posting.google.com>
······@bigfoot.com (Jeff) wrote in message news:<···························@posting.google.com>...
> (perhaps poor analogy, but in java I might do this:
> 
> public class BsxLogger {
>   private String streamName;
>   private FileOutputStream currentStream;
> 
>   public BsxLogger(String stream_name) { ... }
>   public String getStreamName() { return streamName; }
> }

Yes it is a poor analogy because in badly designed programming
languages like this, the class abstraction for defining new types
also, unfortunately, serves as a lexical namespace for resolving
symbol names.

The private/public controls are features of the class-as-a-namespace.

A Lisp class isn't a namespace for resolving symbol names. Of course,
there is a second-level namespace in that symbol *objects* denote
slots.

Lisp provides something like private/public protection at the first
level of resolution, from symbol names to symbols. This is uniformly
available to all language features, even user-defined ones.

You can define a package with your own symbols, and have unexported
symbols within it. When these are referred to using the single colon,
an error is signaled.

  your-package:unexported-symbol   ;; error!

It doesn't matter what UNEXPORTED-SYMBOL is being used for.

Even if you don't use the package system, accessor methods are an
adequate level of protection. It's an inconvenience to use SLOT-VALUE.
Therefore a useful convention is that any slot which can only be
fetched or modified with SLOT-VALUE is considered private. The class
designer provides accessor methods for all slots that are intended to
be manipulated directly.

> so I can't access currentStream from outside this class. This is what
> I want in lisp)

There is no ``inside this class'' in Lisp. Class methods do not have a
special lexical scope that enables unqualified access to slots; there
is no class scope. Moreover, and this is tangential to the point,
class scope is a terribly bad idea.(*)

You can be inside of a package, however, where you have convenient
access to the symbols of that package using unqualified names.

----
(*) If you have a large program with a big class framework, and you
introduce a new member to a base, you are, in a single stroke,
introducing a new binding into every instance of that class scope
throughout that program.

Suppose that a statement like

  z = x + y;

is located in a class scope, and y is a free reference to a global
variable. Now someone unwittingly adds a member called y to the class
scope. The meaning of the expression radically changes.

This problem is one reason why users of these languages adopt naming
conventions like m_* for members, g_* for global variables and the
like. In effect, they are performing renaming to combat the poor
transparency.

In a Lisp program, the analogous expression would not change its
meaning, because all references can be traced down to explicit binding
constructs or else are free. You have WITH-SLOTS, but WITH-SLOTS
explicitly names all of the symbols that are to be bound to the
object's slots. It does not blindly introduce into the lexical scope
bindings for all of the slots in the class. Moreover, it has the
option to use alternate names. The meaning of a WITH-SLOTS expression
won't change if someone adds a new slot.
From: Jeff
Subject: Re: How to make slots private
Date: 
Message-ID: <177accb.0407290706.58cf84c2@posting.google.com>
Thank you, your message is very helpful.