So I come from a {..} background, and I've been reading up details of
CLOS. (must say it is awesome!).
One thing I could not find: In C++ Class definitions, we have public,
protected and private members. Is there anything similar in lisp? If
not, is there a workaround, or is it not needed?
sanket.
··········@gmail.com wrote:
> So I come from a {..} background, and I've been reading up details of
> CLOS. (must say it is awesome!).
>
> One thing I could not find: In C++ Class definitions, we have public,
> protected and private members. Is there anything similar in lisp? If
> not, is there a workaround, or is it not needed?
Depends on what you use hiding for. Self-documentation? To enforce an
API? Probably the closest would be exporting/not exporting symbols from
a package in which the class would be defined. One can also leave off
defining readers and/or writers and then use slot-value internally.
More fundamentally, as a rule Lisp does not try to get correct code out
of people by hog-tying them.
kt
--
Cells: http://common-lisp.net/project/cells/
"I'll say I'm losing my grip, and it feels terrific."
-- Smiling husband to scowling wife, New Yorker cartoon
··········@gmail.com wrote:
> So I come from a {..} background, and I've been reading up details of
> CLOS. (must say it is awesome!).
>
> One thing I could not find: In C++ Class definitions, we have public,
> protected and private members.
This is an artifact of C++ classes having a double role. C++ classes
not only define the structure of run-time objects, but they also serve
as namespaces.
If you declare some x in a class C, then that x is a member of the C
namespace, i.e. it is the symbol C::x. This is a separate relationship
from x being a member of the class C. The latter semantic relationship
can exist independently of namespace containment.
For instance, why couldn't some A::b be a member of class C?
// fictitious example
class C {
typedef int A::b;
double A::z;
};
C++ has namespaces, but it hadn't always. If C++ had had them from the
early beginnings, perhaps classes would work differently today.
If classes didn't serve as namespaces, then it would have to be
namespaces which control access. And namespaces would no longer be
declaration regions, either. You would need a way of declaring that
some name X exists in this namspace, under such and such protection,
without actually declaring that name to have any semantics in the
programming language, because you would actually want to use that name
elsewhere for naming something. A ``symbol'' keyword might do the
trick:
namespace A {
private:
symbol X; // X exists in namespace and is private
public:
symbol Y;
symbol C;
}
Now you can continue withour imaginary flavor of C++ and use the
symbols:
int A::X = 3; // error, we are not in A namespace, and X is private
namespace A {
int A::X; // ok, we are in A.
int X; // unqualified X means A::X
}
class A::C { // C is public, so this is okay
int A::X; // error, we are not in A
void A::Y(); // Good, Y is public in A.
};
namespace A {
class C { // really A::C
int X; // Okay: this is A::X and we are in A
};
}
A::C c; // OK, we are not in A, but C is public.
c.X = 3; // error, no symbol X known in this scope.
c.A::X = 3; // Error, A::X is private
c.A::Y(); // Okay, since Y is public.
using A::Y; // import it
c.Y(); // use unqualified
This gives a kind of gist of how Lisp packages work, or how a very
similar concept might work in C++ syntax. Symbols are entered into
packages, and packages control symbol visibility (external or
internal). When the reader reads Lisp expressions, it always has a
current package context. Unqualified names are resolved with respect to
that current package. Qualified names are used to refer out of the
package.
You can use symbols from any package to name an entity and its
components. A class can be X::Y, with a slot being Z::W. Some method
M::N can be specialized to take a parameter of that class.
In Common Lisp, you can import names that live in a package into
another package.
The internal symbols can always be accessed with a double colon. If S
is internal to package P, then P::S can always refer to it, but P:S is
erroneous. If S is an external symbol in package P, then either P:S or
P::S can refer to it.
Use internal symbols to name things that are intended to be private,
and don't document those things for others to use. Of course, people
can still discover those things and use the double colon to use your
symbols anyway.
··········@gmail.com writes:
> So I come from a {..} background, and I've been reading up details of
> CLOS. (must say it is awesome!).
>
> One thing I could not find: In C++ Class definitions, we have public,
> protected and private members. Is there anything similar in lisp? If
> not, is there a workaround, or is it not needed?
At the class level, as indicated by Lars, you have the possibility to
define various reader, writer or full accessors, or to define your own
methods to access directly the slots with slot-value.
You can also use the packages, and not export some accessors if you
consider them private.
If you have some methods of slots that are really very private, you
can always name them with some convention such as using % or $ in
their name.
And finally, if you want more control on the part of the language, you
can define your own metaclass or your own defclass macro to implement
any kind of access control you fancy.
But actually, it's not needed, in general.
--
__Pascal Bourguignon__ http://www.informatimago.com/
The rule for today:
Touch my tail, I shred your hand.
New rule tomorrow.
··········@gmail.com writes:
> So I come from a {..} background, and I've been reading up details of
> CLOS. (must say it is awesome!).
>
> One thing I could not find: In C++ Class definitions, we have public,
> protected and private members. Is there anything similar in lisp? If
> not, is there a workaround, or is it not needed?
By the way, if you really want to hide a variable, you can do it with
closures (as long as nobody use implementation specific stuff to
access the closure, like system:closure-environment or things like
that..., and as long as nobody breaks in the debugger while in the
closure).
(defun make-protected-data (password)
(let ((hidden 'data)
(key password))
(lambda (message password &optional value)
(if (equal password key)
(ecase message
((set) (setf hidden value))
((get) hidden))
(error "Bad password.")))))
(defparameter *p* (make-protected-data "key"))
[684]> (funcall *p* 'get "key")
DATA
[685]> (funcall *p* 'set "key" 42)
42
[686]> (funcall *p* 'get "key")
42
[687]> (funcall *p* 'get "wrong")
*** - Bad password.
The following restarts are available:
ABORT :R1 ABORT
Break 1 [688]> :bt
<1> #<SYSTEM-FUNCTION EXT:SHOW-STACK> 3
<2> #<COMPILED-FUNCTION SYSTEM::PRINT-BACKTRACE>
<3> #<COMPILED-FUNCTION SYSTEM::DEBUG-BACKTRACE>
<4> #<SYSTEM-FUNCTION SYSTEM::READ-EVAL-PRINT> 2
<5> #<COMPILED-FUNCTION SYSTEM::BREAK-LOOP-2-2>
<6> #<SYSTEM-FUNCTION SYSTEM::SAME-ENV-AS> 2
<7> #<COMPILED-FUNCTION SYSTEM::BREAK-LOOP-2>
<8> #<SYSTEM-FUNCTION SYSTEM::DRIVER>
<9> #<COMPILED-FUNCTION SYSTEM::BREAK-LOOP>
<10> #<SYSTEM-FUNCTION INVOKE-DEBUGGER> 1
<11> #<SYSTEM-FUNCTION ERROR> 1
<12> #<SPECIAL-OPERATOR IF>
EVAL frame for form
(IF (EQUAL PASSWORD KEY)
(LET ((#1=#:G8637 MESSAGE))
(CASE #1# ((SET) (SETQ HIDDEN VALUE)) ((GET) HIDDEN)
(OTHERWISE
(SYSTEM::ETYPECASE-FAILED #1#
(SYSTEM::CASE-ERROR-STRING 'MESSAGE '(SET GET)) '(MEMBER SET GET)))))
(ERROR "Bad password."))
APPLY frame for call (:LAMBDA 'GET '"wrong")
<13>
#<FUNCTION :LAMBDA (MESSAGE PASSWORD &OPTIONAL VALUE)
(IF (EQUAL PASSWORD KEY)
(ECASE MESSAGE ((SET) (SETF HIDDEN VALUE)) ((GET) HIDDEN))
(ERROR "Bad password."))> 2
<14> #<SYSTEM-FUNCTION FUNCALL> 3
EVAL frame for form (FUNCALL *P* 'GET "wrong")
Printed 14 frames
Break 1 [688]> :u
<1> #<SPECIAL-OPERATOR IF>
EVAL frame for form
(IF (EQUAL PASSWORD KEY)
(LET ((#1=#:G8637 MESSAGE))
(CASE #1# ((SET) (SETQ HIDDEN VALUE)) ((GET) HIDDEN)
(OTHERWISE
(SYSTEM::ETYPECASE-FAILED #1#
(SYSTEM::CASE-ERROR-STRING 'MESSAGE '(SET GET)) '(MEMBER SET GET)))))
(ERROR "Bad password."))
Break 1 [688]> key
"key" ; OOOOPPSS!!
Break 1 [688]>
Happily, with clisp, compiling provides one more level of protection:
(defun make-protected-data (password)
(let ((hidden 'data)
(key password))
(compile nil (lambda (message password &optional value)
(if (equal password key)
(ecase message
((set) (setf hidden value))
((get) hidden))
(error "Bad password."))))))
(defparameter *p* (make-protected-data "key"))
[691]> (funcall *p* 'get "wrong")
*** - Bad password.
The following restarts are available:
ABORT :R1 ABORT
Break 1 [692]> :bt
Printed 0 frames
Break 1 [692]> :u
EVAL frame for form (FUNCALL *P* 'GET "wrong")
Break 1 [692]> key
*** - EVAL: variable KEY has no value
The following restarts are available:
USE-VALUE :R1 You may input a value to be used instead of KEY.
STORE-VALUE :R2 You may input a new value for KEY.
ABORT :R3 ABORT
ABORT :R4 ABORT
Break 2 [693]>
But not really :-) :
[697]> (disassemble *p*)
Disassembly of function NIL
(CONST 0) = #(KEY #1="key" HIDDEN DATA #(PASSWORD #1# NIL))
(CONST 1) = 1
(CONST 2) = SET
(CONST 3) = 3
(CONST 4) = GET
(CONST 5) = MESSAGE
(CONST 6) = (SET GET)
(CONST 7) = SYSTEM::CASE-ERROR-STRING
(CONST 8) = (MEMBER SET GET)
(CONST 9) = SYSTEM::ETYPECASE-FAILED
(CONST 10) = "Bad password."
2 required arguments
1 optional argument
No rest parameter
No keyword parameters
32 byte-code instructions:
0 (UNBOUND->NIL 1)
2 (LOAD&PUSH 2)
3 (CONST&PUSH 0) ; #(KEY #1="key" HIDDEN DATA ...)
4 (CONST 1) ; 1
5 (SVREF)
6 (PUSH)
7 (CALLS2&JMPIF 4 L25) ; EQUAL
10 (CONST&PUSH 10) ; "Bad password."
11 (CALLSR 0 29) ; ERROR
14 L14
14 (LOAD&PUSH 1)
15 (CONST&PUSH 0) ; #(KEY #1="key" HIDDEN DATA ...)
16 (CONST 3) ; 3
17 (SVSET)
18 (SKIP&RET 4)
20 L20
20 (CONST&PUSH 0) ; #(KEY #1="key" HIDDEN DATA ...)
21 (CONST 3) ; 3
22 (SVREF)
23 (SKIP&RET 4)
25 L25
25 (LOAD&PUSH 3)
26 (JMPIFEQTO 2 L14) ; SET
29 (LOAD&PUSH 3)
30 (JMPIFEQTO 4 L20) ; GET
33 (LOAD&PUSH 3)
34 (CONST&PUSH 5) ; MESSAGE
35 (CONST&PUSH 6) ; (SET GET)
36 (CALL2&PUSH 7) ; SYSTEM::CASE-ERROR-STRING
38 (CONST&PUSH 8) ; (MEMBER SET GET)
39 (CALL 3 9) ; SYSTEM::ETYPECASE-FAILED
42 (SKIP&RET 4)
NIL
[698]>
And so on...
On a Von Neumann computer, you can always do some peek or poke.
You'd need a real capability based system to really have protected and
private data. Like: http://www.capros.org/ (ex http://www.eros-os.org/).
Or perhaps in a few months: http://dept-info.labri.fr/~strandh/gracle.ps
--
__Pascal Bourguignon__ http://www.informatimago.com/
ATTENTION: Despite any other listing of product contents found
herein, the consumer is advised that, in actuality, this product
consists of 99.9999999999% empty space.
··········@gmail.com wrote:
> So I come from a {..} background, and I've been reading up details of
> CLOS. (must say it is awesome!).
>
> One thing I could not find: In C++ Class definitions, we have public,
> protected and private members. Is there anything similar in lisp? If
> not, is there a workaround, or is it not needed?
There is a distinction between information hiding and encapsulation.
Encapsulation means "you should never ever be able to access that piece
of information even if you really need it", and information hiding means
"you shouldn't need to access this bit of information, and it might
actually change its implementation, but if you know what you're doing,
here is a back door to get to it."
Common Lisp supports information hiding at the package level: You can
define packages that export some symbols and keep other symbols to
themselves. However, the package system is defined in a way that you can
also access internal symbols if you really need to. The package system
is a generic mechanism for controlling access to concepts, it is in fact
just a name space management system. It doesn't matter what you use the
names / symbols for, be it function names, variable names, class names,
slot names, etc. pp.
So the neat thing is that CLOS doesn't need to define any access control
because this is handled orthogonally by the package system. It's also
good to know that when you add your own (domain-specific) language
constructs, you don't have to worry too much about these things either
because you can reuse the package system for your own purposes as well.
There are very few constructs in Common Lisp that support encapsulation.
In general, the assumption in Common Lisp is that programmers are
competent enough to judge the trade offs of respecting or breaking
information hiding.
Pascal
--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/