Some of you may laugh or point me to old arguments about functional
programming versus object-oriented programming. While I'm interested
in reading about that, I have a particular idea that I'm tinkering with
that I'd like some suggestions on. I've looked at existing attempts to
add OOP to LISP, and I don't like them. One of the problems for me is
that they heavily pollute the namespace. If you have a simple data
structure with three variables in it, you're going to get two functions
(get/set) for each variable.
It occurred to me that it might be nice to CLEAN UP the namespace. The
idea that occurred to me was to create a new data type that is a
namespace. What it amounts to is a variable table that is bound to a
variable in another namespace.
Say you wanted to load a socket library. It seems to me like it would
be nice to group all of those calls together into their own namespace.
The SOCKETLIB namespace would be bound to a variable in the global
namespace. When you wanted to access a function in there, you
reference it by namespace and by name. There are also some potential
performance advantages (at least for an interpreter).
Object-oriented programming falls straight out of this. An object is
just a namespace. For class definitions, you'd just have a template
object with a "create" method; calling that would return a reference to
a new namespace that is the object you wanted to create.
One of the wild applications I thought of for this is a MUD engine. In
my interpreter, functions are nothing more than the list that describes
them. You can dynamically generate code and then just execute it.
Internally, when executing a function, I just do an eval on the name of
the function; what comes back is expected to be a function body (with
parameter list). The problem with a MUD engine is security. You want
users to be able to write code, but you don't want them mucking about
with things that don't belong to them. The solution to this is that
functions executing within a given namespace cannot access any other
namespace that they don't know about. Thus, a user would have his own
namespace, allowing him access only to that, any he knows about, and
the global namespace. Sensitive stuff (like the code of the MUD engine
itself) would be in some other namespace. (Oh, and there's also
read-only access to namespaces.)
As I've been adding features that I haven't found in other lisps, I
have managed to come up with reasonably elegant ways to represent them
syntactically. For instance, in LISP, there is a distinction between a
function (which evaluates its arguments) and a special form (which does
not). I have eliminated that distinction by allowing function
definitions to dictate which parameters are to be evaluated. For
instance:
(defun func (eval_param 'uneval_param) body)
In this case, uneval_param is quoted, indicating that the parameter in
the function call should be taken literally. This made things like my
internal definitions of DEFINE, DEFUN, QUOTE, and COND more elegant to
implement.
Back to namespaces. It seems reasonable that a namespace could be
treated, in a sense, like a function. That is, if you want to execute
something in a namespace, you would use this syntax:
(other_namespace (function_in_that_space parameters))
Or, if you wanted to get a variable from a namespace, it would be like
this:
(other_namespace var_in_that_space)
Now to my problem. Given this syntax, I can execute a function whose
body and parameters are in another namespace:
(other_namespace (function_there variable_there))
Here, 'variable_there' has to be in other_namespace. I could also call
a function from another namespace and execute it in the calling
namespace:
((other_namespace function_there) variable_here)
Here, the parameter is evaluated in the calling namespace, but the the
function is also executed in the calling namespace.
The trouble comes down to side-effects. I would like to be able to
call a function from another namespace, passing it parameters evaluated
in the calling namespace, and then execute it in the other namespace.
This way, I can pass in the parameters I like, but side-effects are
confined to the function's home namespace. This is necessary for good
OOP.
I'm trying to come up with a way to represent this that is both
syntactically elegant and easy to implement in my interpreter.
Any suggestions? :)
Requirements vary. Not all OO systems have inheritance.
And not everybody believes it's a terribly useful feature.
Bear
True, but he has an excellent point.
Let me see if I can think up how to do inheritance without changing
anything. How about this: The class (object template) knows about the
class it's inheriting from. When the "make object" function creates an
instance of the class, it will call the "make object" on its parent
class first before setting up its own stuff (which amounts to creating
references to functions and initializing variables). Voila, you have a
composite of multiple objects, with the child class overriding things
belonging to the parent class. Polymorphism isn't much of an issue,
since the object's "type" is nothing more than what's contained in the
namespace.
How's that? :)
I thought about that. In this case, there's nothing much different
between one class inheriting from multiple classes, or one class
inheriting from another class which inherits from another, etc. All
that makes any difference is that whichever constructor gets called
last takes presidence, whenever there's a conflict.
And it's a depth-first construction, so if two classes you inherit from
inherit from the same class, you may get weird results. Well, you can
predict the results, but in any case, you won't get two of the
duplicate base class. Everything's just thrown together into one
namespace.
Of course, none of this is going to matter until I decide how to deal
with my little parameter-passing issue.
Somehow, I need to develop a clean way of doing these three things:
[1] Call function B::F in namespace A, evaluating parameters in
namespace A
[2] Call function B::F in namespace B, evaluating parameters in
namespace B
[3] Call function B::F in namespace A, evaluating parameters in
namespace A
The first two already work. But the third option is important. One
solution is to not have namespace B take over until we've entered the
body of the function being called, but that would overload the syntax
I'm already using for [2].
At the moment, [1] looks like this: ((B F) params)
And [2] looks like this: (B (F params))
Actually, now that I think about it, if we used [2]'s syntax for [3],
we could do [2] like this:
(B (F (B param) (B param2)))
Although that is kinda icky.
If you don't specify a namespace, then you're working in your local
namespace. This looks just like regular LISP code. I guess this is an
implicit this reference. In addition, although I haven't added it yet,
I'm going to provide a built-in function that returns a reference to
the local namespace that you can pass as a parameter to function calls.
Ok, I get it. Do you have any suggestions about how to deal with this?
I'll have to give it some thought.
I'm wondering. What does C++ do about it?
class x {
public:
int x;
};
class y {
public:
string x;
};
class z : public x, public y {
....
};
z my_z;
What does my_z.x give you? A compile-time error?
For that matter, I don't handle this properly either:
class x {
public:
int x;
int something(void) { return x; }
};
class y : public x {
public:
string x;
string anotherthing(void) { return x; }
};
y my_y;
My equivalent to my_y.something() will behave incorrectly.
Note that my LISP implementation has lexical scope across namespace
boundaries but dynamic scope over function calls within the same
namespace. This weirdness is probably going to cause some trouble of
its own.
> psch...@uci.edu wrote:
>> I think you missed my point a little. Let us say you had two classes,
>> one of which used the variable x to store a list of names, and the
>> other used the variable x to store a number. If you inherit from both
>> of these classes there will be problems, for example if one class has a
>> method that computes (+ x 2.5) and another that does (set! x (append x
>> '(b))) ugly errors will arrise. At least it seems to me that is what
>> would happen.
>
> Ok, I get it. Do you have any suggestions about how to deal with this?
> I'll have to give it some thought.
>
> I'm wondering. What does C++ do about it?
Well, C++ is statically typed. Having a member x of type int in one
mixin and a member x of type char* in another would be a problem if
those x weren't in different namespaces.
But in lisp, since we're dynamically typed, you can easily unify same
named slots. Or do whatever you want. You could keep both slots.
CLOS unify them:
[4]> (defclass a () (x))
#<STANDARD-CLASS A>
[5]> (defclass b () (x))
#<STANDARD-CLASS B>
[8]> (defclass c (a b) ())
#<STANDARD-CLASS C>
[9]> (inspect (make-instance 'c))
#<COMMON-LISP-USER::C #x204F93B6>: standard object
type: COMMON-LISP-USER::C
0 [X]: |#<unbound>|
INSPECT-- type :h for help; :q to return to the REPL ---> :q
Only one slot named X in a C instance.
But it's even more different. In C++, you have this idea of data
hidding, and methods belong to classes.
In CLOS, for example, but I believe in general in lisp too, there's
less data hidding going on: the programmers are considered adult, with
responsibility to have a look at the mixins sources and to act
responsably. In CLOS, methods are not attached to the classes, but to
generic functions.
First, instead of accessing the slot directly with slot-value, you
usually go thru an accessor function. Either with naming conventions
or the use of packages, slot names and accessors are usually easily
distinguished. But assuming you keep the same slot name:
[13]> (defclass a () ((x :accessor a-x)))
#<STANDARD-CLASS A :VERSION 1>
[14]> (defclass b () ((x :accessor b-x)))
#<STANDARD-CLASS B :VERSION 1>
[15]> (defclass c (a b) ((x :accessor c-x)))
#<STANDARD-CLASS C :VERSION 3>
[16]> (setf o (make-instance 'c))
#<C #x205222E6>
[17]> (setf (a-x o) 1)
1
[18]> (b-x o)
1
[19]> (setf (b-x o) '(a b c))
(A B C)
[20]> (a-x o)
(A B C)
[21]> (c-x o)
(A B C)
We have therefore three accessors for the same unified slot. But we
can write our own accessor, to dispatch to one of the mixin accessors:
[22]> (defmethod (setf c-x) ((value number) (object c))
(setf (a-x object) value))
#<STANDARD-METHOD (#<BUILT-IN-CLASS NUMBER> #<STANDARD-CLASS C :VERSION 3>)>
[23]> (defmethod (setf c-x) ((value string) (object c))
(setf (b-x object) value))
#<STANDARD-METHOD (#<BUILT-IN-CLASS STRING> #<STANDARD-CLASS C :VERSION 3>)>
which can be indicated when these mixin accessors do more than merely
assigning the slot, and when only one aspect of that slot is used at a
time.
Otherwise, if all the methods working on the mixin classes access the
slot x thru the corresponding accessors (a-x or b-x), you can merely
redefine these accessor. Well actually this doesn't "redefining"
them, this only add a new method to the corresponding generic
function:
[24]> (defclass c (a b) ((a-x :accessor a-x) (b-x :accessor b-x)))
WARNING: The generic function #<STANDARD-GENERIC-FUNCTION C-X> is being
modified, but has already been called.
WARNING: Removing method
#<CLOS:STANDARD-READER-METHOD (#<STANDARD-CLASS C :VERSION 3>)>
in #<STANDARD-GENERIC-FUNCTION C-X>
WARNING: DEFCLASS: Class C (or one of its ancestors) is being redefined,
instances are obsolete
WARNING: The generic function #<STANDARD-GENERIC-FUNCTION A-X> is being
modified, but has already been called.
WARNING: The generic function #<STANDARD-GENERIC-FUNCTION (SETF A-X)> is being
modified, but has already been called.
WARNING: The generic function #<STANDARD-GENERIC-FUNCTION B-X> is being
modified, but has already been called.
WARNING: The generic function #<STANDARD-GENERIC-FUNCTION (SETF B-X)> is being
modified, but has already been called.
#<STANDARD-CLASS C :VERSION 4>
[25]> (setf o (make-instance 'c))
#<C #x205411CE>
[26]> (inspect o)
#<COMMON-LISP-USER::C #x205411CE>: standard object
type: COMMON-LISP-USER::C
0 [X]: |#<unbound>|
1 [A-X]: |#<unbound>|
2 [B-X]: |#<unbound>|
INSPECT-- type :h for help; :q to return to the REPL ---> :q
[27]> (setf (a-x o) 1 (b-x o) "one")
"one"
[28]> (inspect o)
#<COMMON-LISP-USER::C #x205411CE>: standard object
type: COMMON-LISP-USER::C
0 [X]: |#<unbound>|
1 [A-X]: 1
2 [B-X]: "one"
INSPECT-- type :h for help; :q to return to the REPL ---> :q
[29]> (a-x o)
1
[30]> (b-x o)
"one"
[31]> (setf oa (make-instance 'a))
#<A #x2054EFAE>
[32]> (setf (a-x oa) 42)
42
[33]> (inspect oa)
#<COMMON-LISP-USER::A #x2054EFAE>: standard object
type: COMMON-LISP-USER::A
0 [X]: 42
INSPECT-- type :h for help; :q to return to the REPL ---> :q
[34]>
So in conclusion, in lisp you have the opportunity to implement
whatever object system you want, and you would be well advised to
check other object models than that of C++. Have a look at CLOS
(Common Lisp), at KR, at Smalltalk! ( You can find KR in Garnet
http://sf.net/projects/garnetlisp ).
And don't forget to google for: scheme object
--
__Pascal Bourguignon__ http://www.informatimago.com/
ADVISORY: There is an extremely small but nonzero chance that,
through a process known as "tunneling," this product may
spontaneously disappear from its present location and reappear at
any random place in the universe, including your neighbor's
domicile. The manufacturer will not be responsible for any damages
or inconveniences that may result.
and why is this an extension to the 20 different post methods rather
than an extension to the one edit method? Because you have analyzed
the problem badly.
The argument against inheritance is that it allows bad methodology and
problem analysis to create complex, difficult-to-maintain "spaghetti
classes" - in this particular example trading 20 different inheritance
paths for one.
Each and every inheritance path is a logical path that someone
will have to navigate someday when they try to maintain the code;
automating their creation in such a way that it happens casually or
carelessly, especially when it is used instead of good design,
increases the burden on maintainers.
Bear
> Each and every inheritance path is a logical path that someone
> will have to navigate someday when they try to maintain the code;
> automating their creation in such a way that it happens casually or
> carelessly, especially when it is used instead of good design,
> increases the burden on maintainers.
What would be a better design?
No, the assumption is that multiple methods may call the one post
method. Not all of them may be the edit method. The point is that we
are building on an object with functionality implemented for other
pourposes, we are engaging in code re-use vs. rewriting it from
scratch. It seems like you want to simply rewrite the class from
scratch, as you haven't proposed a better method. Let me make the
example more concrete: The class contains methods post, which does the
sending of text, and well as create-new-post and respond-to-post, both
of which call post internally. I am suggesting that we would inherit
from this class, so that we only have to change the post method in our
new class to append our sig. I am willing to hear other ideas that
would let us do the same thing wihtout rewriting the class from
scratch. Basically if you are going to use objects in your programming
at some point you will want inheritance if you wish to reuse exising
code without writing it over again.
> As I've been adding features that I haven't found in other lisps, I
> have managed to come up with reasonably elegant ways to represent them
> syntactically. For instance, in LISP, there is a distinction between a
> function (which evaluates its arguments) and a special form (which does
> not). I have eliminated that distinction by allowing function
> definitions to dictate which parameters are to be evaluated. For
> instance:
> (defun func (eval_param 'uneval_param) body)
> In this case, uneval_param is quoted, indicating that the parameter in
> the function call should be taken literally. This made things like my
> internal definitions of DEFINE, DEFUN, QUOTE, and COND more elegant to
> implement.
What happens if you pass func to other functions? e.g.
(map func (list a b c) (list d e f))
Naturally, one would expect the result would be the same
as the result of:
(list (func a d) (func b e) (func c f))
But I don't know how to do that without very strong type inference
and whole program analysis. I think this is one of the reasons
most lisps don't implement the feature.
(I think I've seen somebody in c.l.s implementing similar feature,
though... I'm curious how it has come out.)
--shiro
I've got two options that come to mind:
[1] So far, I've been passing around a reference to a single namespace
as a "context". I can make contexts have two namespaces. One is for
evaluating parameters; one is for calling functions. When a function
is applied, the parameter eval namespace is switched for the function
call namespace so that the called function evaluates parameters to
functions it calls in its own namespace. (Lexical scope on parameter
passing, essentially.)
The problem with [1] is that if you want to pass a function as a
parameter it will not be executed in its own namespace.
[2] When a function (well, just a list) is looked up, somehow, its home
namespace is attached so that when it's executed, it gets run in its
home namespace. This way, I can pass a function as a parameter, and
side-effects of that function occur in the namespace that function was
looked up from. In order to pass a function and have it run in any
arbitrary namespace, I would have to provide a builtin that strips it
of a namespace reference.
Implementing [1] is how I described it: (namespace (funcname params))
But [2] would overload the other syntax: ((namespace funcname) params)
But with [2], I have to provide a way to execute a function, looked up
in another namespace, but run in the local namespace. It would look
like this: ((detach (namespace funcname)) params)
The way I've organized memory makes it hard to attach that kind of
metadata to something. I thought about making an INDIRECT data type
that internally was just a dotted pair, car being the object of
interest, cdr being a reference to the namespace. The trouble is that
since I don't distinguish between lists and functions, any time I
wanted to process a list somewhere, I'd have to add code to strip off
the indirect reference. Pain and performance loss. You'll see my
problem when you have a look at my internal representation of a cell:
typedef int cell_t;
typedef long long int64;
typedef long double real80;
typedef cell_t (*fptr_t)(cell_t s, cell_t alist);
typedef union _s_expr {
struct {
unsigned int type : 4;
unsigned int flag0 : 1;
unsigned int quoted : 1;
unsigned int refct : 26;
};
struct {
int dummy;
int64 val;
} integer;
struct {
int dummy;
real80 val;
} real;
struct {
int dummy;
cell_t a, d, b;
} pair;
struct {
int dummy;
char *ptr;
union {
int len;
cell_t unq; /* reference to unquoted form of symbol */
};
} string; /* and symbol */
struct {
int dummy;
fptr_t f;
unsigned int pflags;
} fptr;
} s_expr_t;
It's exactly 128 bits. The 'b' reference for a pair is actually an
extension I made in order to make binary trees easier to implement (and
CBR is the function to call to get this 'middle' branch), so I use it
all over the place. Instead of association lists, I have association
trees, making lookups O(log(n)) time. I cannot just hide in there
namespace metadata without making the structure an uneven number of
words.
I realize that I should not let low-level decisions hurt the over-all
design of the language, but there's are some advantages to having
power-of-two sized data structures.
Suggestions?
P.S. I got some great ideas from a classmate on memory organization.
Adding some ideas of my own, memory cells are not malloc'd
individually. Instead, they're allocated in blocks of 256, and cells
are referenced by index instead of pointer. I have an expandable array
of pointers to blocks of 256. Now, since cell numbers are just
integers, this guy gave me the idea to encode 31-bit integers in cell
numbers so that they effectively don't take up any space. Here's my
macro to decode an int:
INLINE long long get_int(cell_t n)
{
if (n & INTMASK) {
if (n & INTSIGN) {
return (int64)n;
} else {
return (int64)(n ^ INTMASK);
}
}
if (n < RESERVED_CELLS) int_error();
return GCELL(n).integer.val;
}
There's no "One Way" of handling multiple inheritance, neither one way
to design an object system.
If you have these requirements, surely you can come with a solution.
Here is another question you'll have to resolve:
(defclass a () (x))
(defclass b (a) (y))
(defclass c (a) (z))
;; up to now, no collision.
(defclass d (b c))
What should be done here? D inherits from both B and C, and they both
inherit from A.
What if D considered as a B instance wants to impose a different
invariant on X than D considered as a C instance?
On some systems, the A part of the mixin is duplicated.
In others it's unified.
You can come with real-life examples where both solutions would be right:
(defclass named () (name))
(defclass physical (named) (mass))
(defclass storable (named) (size))
(defclass stophy (storable physical) ())
Here, a stophy has only one name, as a named object.
( One could argue the class graph is wrong and should be done as:
(defclass named () (name))
(defclass massive () (mass))
(defclass sizeable () (size))
(defclass physical (named massive) ())
(defclass storable (named sizeable) ())
(defclass stophy (named massive sizeable) ())
This is to say, don't have attribute collisions in mixin!
)
or:
(defclass mobile () (speed))
(defclass flying (mobile) (altitude))
(defclass running (mobile) (resistance))
(defclass albatros (flying running))
Here, an albatros can run at one speed, and can fly at another...
The class graphs programmers will have to designs will depend on your
object system rules.
--
__Pascal Bourguignon__ http://www.informatimago.com/
"Indentation! -- I will show you how to indent when I indent your skull!"
You can create namespaces:
> (define ns (namespace))
If you want to work entirely within a namespace, then use the namespace
like a function name:
> (ns (define var 3))
3
To access something in a namespace, do the same thing:
> (ns var)
3
You can add functions to a namespace:
> (ns (defun q (x y) (+ x y)))
Q
You can execute code entirely within a namespace and call functions in
that namespace:
> (ns (define a 3) (define b 4) (q a b))
7
Often, however, you'll want to execute a function in another namespace,
but you want to get evaluate parameters from the calling namespace.
Internally, whenever you look up a function, it brings along with it
information about its home namespace. Eval for anything lexically in
the function call is done in the calling namespace, but inside of the
function, the namespace used is the home namespace of the function:
> (define a 6)
6
> (define b 7)
7
> ((ns q) a b)
13
Let's say you have a function that has side-effects. For instance, it
sets variables in its namespace:
> (ns (defun q (x y) (set z (+ x y))))
Q
If you call it the way I mentioned above, the variable z will be set in
the ns namespace:
> ((ns q) 3 4)
7
> (ns z)
7
But what if you want to execute it in the current namespace?
> ((detach (ns q)) 3 4)
7
> z
7
How about running a function from namespace D in namespace E?
> ((attach e (d q)) 3 4)
{whatever}
On top of this, a semi-object system can be built, although its
original purpose was a security feature for a MUD engine. :)
It's certainly far from complete.
Thoughts?
Another benefit to this method is that parameters can be from different
namspaces. Consider the situation where you have a function my-map
defined in A, the function you wish to pass it in B, and the list in C.
I don't know how you would make this work under your current model,
but under my suggestion it should look like: (my-map (B func) (C lst))
This shouldn't affect a function defined in ns that sets a varaible z,
no matter where it is called from the z it sets should be in ns,
because references are bound when the function is created. This is a
similiar situation to a function defined as follows
(define func
(let ((x 0))
(lambda () (set! x (+ x 1)) x)))
Even if x is defined where this function is called the only x that will
be set and returned is the one in the let.
-Peter: http://pschombe.wordpress.com/
I think I was unclear in my explanation. First of all, "(let ((x 4) (y
7)) (my-plus x y))" doesn't reference a namespace, so everything will
happen in the current namespace. But say my-plus were in namespace ns,
and you called it as "(let ((x 4) (y 7)) ((ns my-plus) x y))". It
would behave exactly the same way. x and y would be evaluated in the
current namespace.
The only difference between the two would be if my-plus had any
side-effects. In the first case, obviously, it would affect the local
namespace, because that's where the function lives. In the second
case, the side-effects would affect the namespace that contains the
my-plus function. Nevertheless, the parameters are evaluated in the
calling namespace in both cases.
You example, "(my-plus (ns x) (ns y))", would work just fine. It would
call a function in the local namespace, getting parameters from another
namespace.
>
> Another benefit to this method is that parameters can be from different
> namspaces. Consider the situation where you have a function my-map
> defined in A, the function you wish to pass it in B, and the list in C.
> I don't know how you would make this work under your current model,
> but under my suggestion it should look like: (my-map (B func) (C lst))
Let's say you're in the default namespace (TOP), my-map is in A, func
is in B, and lst is in C, and x is in TOP. In that case, the syntax
would be:
((A my-map) (B func) (C lst) x)
my-map would be fetched from A, but it would have A associated with it.
Func would be fetched from B, and it would have B associated with it.
lst is just a list so it's just fetched from C. And x is fetched from
TOP. Next, when A::my-map is running, it wants to call func. func's
parameters would be evaluated in A, where my-map lives, but when
executing func, it would run in the context of B, where func lives.
>
> This shouldn't affect a function defined in ns that sets a varaible z,
> no matter where it is called from the z it sets should be in ns,
Yes. Unless you dissociate the function from ns, which you can do, by
stripping it of its namespace.
> because references are bound when the function is created. This is a
> similiar situation to a function defined as follows
> (define func
> (let ((x 0))
> (lambda () (set! x (+ x 1)) x)))
> Even if x is defined where this function is called the only x that will
> be set and returned is the one in the let.
Yes, this is analogous.
> The title is perhaps a bit misleading. I took at grad course in
> programming languages where we had to develop a LISP interpreter.
> Since then, I've been tinkering with it for fun, doing things to it.
> And since I like Scheme more than LISP, it's evolved to be more like
> Scheme than LISP. I'm betting that nothing I mention here is new, but
> on the other hand, I haven't found much about it, so please forgive me
> for being redundant. Since what I'm doing is very academic in nature,
> I'm not overly concerned with making a compilable language.
What kind of LISP are you talking about. Nowadays
we would write Lisp and this is a family of programming
languages. Members of this family are Scheme, Common Lisp,
ISLisp, EuLisp, AutoLisp and so on.
> Some of you may laugh or point me to old arguments about functional
> programming versus object-oriented programming. While I'm interested
> in reading about that, I have a particular idea that I'm tinkering with
> that I'd like some suggestions on. I've looked at existing attempts to
> add OOP to LISP, and I don't like them.
Which ones are you talking about? A typical Lisp with object-oriented
features is Common Lisp.
> One of the problems for me is
> that they heavily pollute the namespace. If you have a simple data
> structure with three variables in it, you're going to get two functions
> (get/set) for each variable.
In Common Lisp this is not true. You have to specify
the names of GET/SET functions if you want them.
You can name these functions in any namespace (called packages)
you want. You can even avoid putting the function names
into a namespace (package).
> It occurred to me that it might be nice to CLEAN UP the namespace. The
> idea that occurred to me was to create a new data type that is a
> namespace.
Called 'package' in Common Lisp.
> What it amounts to is a variable table that is bound to a
> variable in another namespace.
>
> Say you wanted to load a socket library. It seems to me like it would
> be nice to group all of those calls together into their own namespace.
Yes, that is a package in Common Lisp.
> The SOCKETLIB namespace would be bound to a variable in the global
> namespace. When you wanted to access a function in there, you
> reference it by namespace and by name. There are also some potential
> performance advantages (at least for an interpreter).
There are no performance advantages.
...
> As I've been adding features that I haven't found in other lisps, I
> have managed to come up with reasonably elegant ways to represent them
> syntactically. For instance, in LISP, there is a distinction between a
> function (which evaluates its arguments) and a special form (which does
> not).
That's wrong.
It is not the function that evaluates the arguments.
Actually the function will be called with already evaluated arguments.
> I have eliminated that distinction by allowing function
> definitions to dictate which parameters are to be evaluated. For
> instance:
> (defun func (eval_param 'uneval_param) body)
> In this case, uneval_param is quoted, indicating that the parameter in
> the function call should be taken literally. This made things like my
> internal definitions of DEFINE, DEFUN, QUOTE, and COND more elegant to
> implement.
That was many years ago common in various Lisp dialects.
It has been removed for several reasons. Scheme and
Common Lisp don't provide this. There are only a very
small amount of built-in and non-extensible special
forms. The rest are macros and ordinary function.
> Back to namespaces. It seems reasonable that a namespace could be
> treated, in a sense, like a function. That is, if you want to execute
> something in a namespace, you would use this syntax:
> (other_namespace (function_in_that_space parameters))
> Or, if you wanted to get a variable from a namespace, it would be like
> this:
> (other_namespace var_in_that_space)
I don't want this. A namespace is a construct that should
only be use a READ time.
You are probably confusing 'namespaces' and 'environments'.
> Now to my problem. Given this syntax, I can execute a function whose
> body and parameters are in another namespace:
> (other_namespace (function_there variable_there))
> Here, 'variable_there' has to be in other_namespace. I could also call
> a function from another namespace and execute it in the calling
> namespace:
> ((other_namespace function_there) variable_here)
> Here, the parameter is evaluated in the calling namespace, but the the
> function is also executed in the calling namespace.
>
> The trouble comes down to side-effects. I would like to be able to
> call a function from another namespace, passing it parameters evaluated
> in the calling namespace, and then execute it in the other namespace.
> This way, I can pass in the parameters I like, but side-effects are
> confined to the function's home namespace. This is necessary for good
> OOP.
>
> I'm trying to come up with a way to represent this that is both
> syntactically elegant and easy to implement in my interpreter.
>
> Any suggestions? :)
Yes, if you don't have it, run not walk and buy
Christian Queinnec's Book 'Lisp' (Lisp in Small Pieces).
> What kind of LISP are you talking about. Nowadays
> we would write Lisp and this is a family of programming
> languages. Members of this family are Scheme, Common Lisp,
> ISLisp, EuLisp, AutoLisp and so on.
Yes, and I've made up my own. I suppose you think that's silly. I
think it's a good way to learn to understand these things. For
instance, in another thread, I'd asked about the value of using the
LAMBDA keyword. I mean, my interpreter doesn't need any syntactic
sugar to interpret a list. But it turned out that in order to
implement what I was calling "namespaces", it became necessary to
require LAMBDA. For that matter, doing it myself has helped me to
appreciate many of the design decisions in various Lisps, and I've
gotten much better at writing Lisp functions in the process.
>
> > Some of you may laugh or point me to old arguments about functional
> > programming versus object-oriented programming. While I'm interested
> > in reading about that, I have a particular idea that I'm tinkering with
> > that I'd like some suggestions on. I've looked at existing attempts to
> > add OOP to LISP, and I don't like them.
>
> Which ones are you talking about? A typical Lisp with object-oriented
> features is Common Lisp.
I am somewhat familiar with CLOS, but I felt that it polluted the
namespace. That is, in your current environment, there are lots and
lots of extra names. If you want access to variables in an object, you
end up with a pair of extra function names. It just seems cluttery to
me.
> > One of the problems for me is
> > that they heavily pollute the namespace. If you have a simple data
> > structure with three variables in it, you're going to get two functions
> > (get/set) for each variable.
>
> In Common Lisp this is not true. You have to specify
> the names of GET/SET functions if you want them.
> You can name these functions in any namespace (called packages)
> you want. You can even avoid putting the function names
> into a namespace (package).
I suppose I'll have to look into packages. Of course, there are other
things about Lisp that bother me. For instance, in Clisp, you specify
optional parameters, IIRC, like this: (p1 p2 &optional rest). In
Scheme, you do it this way: (p1 p2 . rest). I think I like the Scheme
way much better.
Oh, and don't get me started on the for loop construct: (loop for idx
from 0 upto length by 5 ...
It's nothing but excess clutter. Absolutely as far away from
minimalist as possible. Nothing but syntactic sugar. The 'for',
'from', 'upto', and 'by' keywords there are absolutely unnecessary.
Yes, I have learned the value of a bit of syntactic sugar. Someone in
the other thread pointed me to a truly minimalist language, and it was
quite unusable. Might as well be writing assembly code. But this loop
construct just strikes me as going too far. Lisp has a certain flavor
to its syntax and the way things are represented, and this loop
construct just violates it. It makes it look like Pascal or something.
Pascal is all about syntactic sugar and salt, but we're not trying to
limit ourselves to 'teaching' languages here.
> > It occurred to me that it might be nice to CLEAN UP the namespace. The
> > idea that occurred to me was to create a new data type that is a
> > namespace.
>
> Called 'package' in Common Lisp.
Fair enough. But to me 'package' strikes me as something you load like
a library. Like when you compile a C program and put -lsocket on the
linker line. While that is one of my intended purposes, what I'm
calling a namespace attempts to embody what you are calling 'package',
'namespace', and 'environment' into one construct. Isn't getting a lot
out of a single idea a virtue in programming language design?
> > The SOCKETLIB namespace would be bound to a variable in the global
> > namespace. When you wanted to access a function in there, you
> > reference it by namespace and by name. There are also some potential
> > performance advantages (at least for an interpreter).
>
> There are no performance advantages.
Perhaps not in clisp. In my case, since it's interpreted, there
definitely are performance advantages. Compare searching one huge
association list versus searching through a few small ones like a tree
structure. If it's compiled, then you're right. If not, then I'm
right.
(Mind you, my 'association lists' are binary trees that rebalance based
on the most frequently accessed items, but that's beside point.)
> That's wrong.
>
> It is not the function that evaluates the arguments.
> Actually the function will be called with already evaluated arguments.
That is precisely how my interpreter is structured. When calling a
function, arguments are evaluated and then passed to the function.
However, I thought I was clear enough from the context.
>
> > I have eliminated that distinction by allowing function
> > definitions to dictate which parameters are to be evaluated. For
> > instance:
> > (defun func (eval_param 'uneval_param) body)
> > In this case, uneval_param is quoted, indicating that the parameter in
> > the function call should be taken literally. This made things like my
> > internal definitions of DEFINE, DEFUN, QUOTE, and COND more elegant to
> > implement.
>
> That was many years ago common in various Lisp dialects.
> It has been removed for several reasons. Scheme and
> Common Lisp don't provide this. There are only a very
> small amount of built-in and non-extensible special
> forms. The rest are macros and ordinary function.
Why was it removed? Care to give some reasons? Did macros make it
obsolete? I had two reasons for doing it this way. One is that
special forms and functions could be unified. The other is that it was
a step towards unifying those with macros.
>
> > Back to namespaces. It seems reasonable that a namespace could be
> > treated, in a sense, like a function. That is, if you want to execute
> > something in a namespace, you would use this syntax:
> > (other_namespace (function_in_that_space parameters))
> > Or, if you wanted to get a variable from a namespace, it would be like
> > this:
> > (other_namespace var_in_that_space)
>
> I don't want this.
You don't have to have it. This is my interpreter, not yours. :)
> A namespace is a construct that should
> only be use a READ time.
That all depends on how you define 'namespace', now doesn't it?
Perhaps I should have chosen a different word, but I think it's quite
clear from context what I mean.
> You are probably confusing 'namespaces' and 'environments'.
Yes, I think, for some purposes, 'environment' might be clearer, but it
does not fully embody everything that my 'namespaces' are capable of.
> > Any suggestions? :)
>
> Yes, if you don't have it, run not walk and buy
> Christian Queinnec's Book 'Lisp' (Lisp in Small Pieces).
I've looked into it, although I do already have some books on Lisp. I
appreciate the pointer, but I get the feeling that you're pointing me
at this as a way to tell me that existing Lisps are 'right' and that my
implementation is wrong. So either I'm a complete idiot and need to be
educated, or you aren't bothering to think past what you're accustomed
to and think about alternative ways of representing things. Which is
it? (Of course, I'm perfectly willing to consider that it's a
combination of the two or a third option entirely...)
You don't know enough Common Lisp then!
(defclass c ()
((a)
(b)
(c)))
There's no function named a, b or c generated by this class.
To access the slots, you must use the namespace operator:
(let ((o (make-instance 'c)))
(setf (slot-value o 'a) "Ah")
(inspect o))
-->
#<COMMON-LISP-USER::C #x2069577E>: standard object
type: COMMON-LISP-USER::C
0 [A]: "Ah"
1 [B]: |#<unbound>|
2 [C]: |#<unbound>|
If you don't like the name, you can always write:
(defmacro namespace (o slot) `(slot-value ,o ,slot))
(setf (namespace o 'a) "Ah")
(print (namespace o 'a))
> I suppose I'll have to look into packages.
Please, do.
> Of course, there are other
> things about Lisp that bother me. For instance, in Clisp, you specify
> optional parameters, IIRC, like this: (p1 p2 &optional rest). In
> Scheme, you do it this way: (p1 p2 . rest). I think I like the Scheme
> way much better.
You can also use (p1 p2 . rest) in Common Lisp, not only in clisp
(which is but one implementation of Common Lisp):
(defmacro mydefun (name args &body body)
`(defun ,name
,(labels ((cutdot (list proper)
(cond
((null list) (list (nreverse proper)))
((consp list) (cutdot (cdr list) (cons (car list) proper)))
(t (list (nreverse proper) (list list))))))
(destructuring-bind (butdot dot) (cutdot args '())
(if dot
(append butdot (list '&rest (car dot)))
butdot)))
,@body))
(macroexpand-1 '(mydefun f (a b . rest) (list a b rest)))
--> (DEFUN F (A B &REST REST) (LIST A B REST)) ;
> Oh, and don't get me started on the for loop construct: (loop for idx
> from 0 upto length by 5 ...
> It's nothing but excess clutter. Absolutely as far away from
> minimalist as possible. Nothing but syntactic sugar. The 'for',
> 'from', 'upto', and 'by' keywords there are absolutely unnecessary.
Then don't use it! You've got DOTIMES, DOLIST, DO, DO*, etc.
> Yes, I have learned the value of a bit of syntactic sugar. Someone in
> the other thread pointed me to a truly minimalist language, and it was
> quite unusable. Might as well be writing assembly code. But this loop
> construct just strikes me as going too far. Lisp has a certain flavor
> to its syntax and the way things are represented, and this loop
> construct just violates it. It makes it look like Pascal or something.
> Pascal is all about syntactic sugar and salt, but we're not trying to
> limit ourselves to 'teaching' languages here.
You'll like the ITERATE package. It has a much more lisp-like look.
> > > The SOCKETLIB namespace would be bound to a variable in the global
> > > namespace. When you wanted to access a function in there, you
> > > reference it by namespace and by name. There are also some potential
> > > performance advantages (at least for an interpreter).
> >
> > There are no performance advantages.
>
> Perhaps not in clisp. In my case, since it's interpreted, there
> definitely are performance advantages. Compare searching one huge
> association list versus searching through a few small ones like a tree
> structure. If it's compiled, then you're right. If not, then I'm
> right.
In clisp:
[53]> (let ((alist (loop for i to 1000000 collect (cons i i))))
(time (loop repeat 100 do (assoc 1000000 alist))))
Real time: 113.578575 sec.
Run time: 9.564598 sec.
Space: 4256 Bytes
NIL
In SBCL:
* (let ((alist (loop for i to 1000000 collect (cons i i))))
(time (loop repeat 100 do (assoc 1000000 alist))))
Evaluation took:
57.583 seconds of real time
5.480343 seconds of user run time
0.004 seconds of system run time
0 page faults and
0 bytes consed.
NIL
*
With clisp interpreted, assoc doesn't even take twice the time sbcl compiled!
(Don't mind the real time, I've got a busy machine).
On the contrary, if you're trying to search complex data structures,
with interpreted code it'll be much slower than with compiled code.
You can make benchmark of any data structure, the breakpoint is always
greater under an interpreter than a compiler. You can use sequencial
data structures with interpreted code with N up to ~30 before it
becomes interesting to switch to hash tables for example, while with
compiled code, the limit is ~5.
>> > I have eliminated that distinction by allowing function
>> > definitions to dictate which parameters are to be evaluated. For
>> > instance:
>> > (defun func (eval_param 'uneval_param) body)
>> > In this case, uneval_param is quoted, indicating that the parameter in
>> > the function call should be taken literally. This made things like my
>> > internal definitions of DEFINE, DEFUN, QUOTE, and COND more elegant to
>> > implement.
>>
>> That was many years ago common in various Lisp dialects.
>> It has been removed for several reasons. Scheme and
>> Common Lisp don't provide this. There are only a very
>> small amount of built-in and non-extensible special
>> forms. The rest are macros and ordinary function.
>
> Why was it removed?
It was a mess.
> Care to give some reasons?
It was hard to know what was a function, what a macro, what a special operator,
so it was hard to reason about a program.
It didn't help to maintain same semantics in interpreted code as in
compiled code either.
> Did macros make it obsolete?
Indeed, macros offered a structured way to do all you did with subrs,
fsubrs,, exprs and fexprs.
> I had two reasons for doing it this way. One is that special forms
> and functions could be unified. The other is that it was a step
> towards unifying those with macros.
Macros are already mere functions, and special operators are no
functions at all. There's no further need for unification.
>> Yes, if you don't have it, run not walk and buy
>> Christian Queinnec's Book 'Lisp' (Lisp in Small Pieces).
>
> [...] (Of course, I'm perfectly willing to consider that it's a
> combination of the two or a third option entirely...)
It's the third.
http://www-spi.lip6.fr/~queinnec/WWW/LiSP.html
--
__Pascal Bourguignon__ http://www.informatimago.com/
Nobody can fix the economy. Nobody can be trusted with their finger
on the button. Nobody's perfect. VOTE FOR NOBODY.
> Yes, and I've made up my own. I suppose you think that's silly.
No, not really. You should just try to read the relevant
Lisp literature while you try to implement your own.
> I am somewhat familiar with CLOS, but I felt that it polluted the
> namespace.
It does not.
> That is, in your current environment, there are lots and
> lots of extra names. If you want access to variables in an object, you
> end up with a pair of extra function names. It just seems cluttery to
> me.
You should understand that there is a different meaning
to the word 'environment' and 'package'. In Common Lisp
names of functions are in packages.
As I said, by default there are no functions defined if you
define a class in Common Lisp. If you want accessors you
have to name them and you have to choose the names yourself.
> I suppose I'll have to look into packages. Of course, there are other
> things about Lisp that bother me. For instance, in Clisp, you specify
> optional parameters, IIRC, like this: (p1 p2 &optional rest). In
> Scheme, you do it this way: (p1 p2 . rest). I think I like the Scheme
> way much better.
'rest' parameters and 'optional' parameters
are different. Scheme has 'rest' parameters. Common Lisp
has both 'rest' and 'optional' parameters. Common Lisp
also has keyword parameters. Whether you write (foo . bar)
or (foo &rest bar) is really a very minor point.
>
> Oh, and don't get me started on the for loop construct: (loop for idx
> from 0 upto length by 5 ...
> It's nothing but excess clutter. Absolutely as far away from
> minimalist as possible. Nothing but syntactic sugar. The 'for',
> 'from', 'upto', and 'by' keywords there are absolutely unnecessary.
You don't have to use it. There are alternatives in Common Lisp.
> Yes, I have learned the value of a bit of syntactic sugar. Someone in
> the other thread pointed me to a truly minimalist language, and it was
> quite unusable. Might as well be writing assembly code. But this loop
> construct just strikes me as going too far. Lisp has a certain flavor
> to its syntax and the way things are represented, and this loop
> construct just violates it. It makes it look like Pascal or something.
> Pascal is all about syntactic sugar and salt, but we're not trying to
> limit ourselves to 'teaching' languages here.
>
> > > It occurred to me that it might be nice to CLEAN UP the namespace. The
> > > idea that occurred to me was to create a new data type that is a
> > > namespace.
> >
> > Called 'package' in Common Lisp.
>
> Fair enough. But to me 'package' strikes me as something you load like
> a library.
That's just you. Something like a library does not exist in
Common Lisp, the nearest thing is the 'module' in Common Lisp.
> Like when you compile a C program and put -lsocket on the
> linker line. While that is one of my intended purposes, what I'm
> calling a namespace attempts to embody what you are calling 'package',
> 'namespace', and 'environment' into one construct. Isn't getting a lot
> out of a single idea a virtue in programming language design?
Packages are namespaces. Environments is something other. Common Lisp
often (not always) tries to give you really the orthogonal constructs
instead of only giving you some complex thing that combines
various unrelated things into one.
> Perhaps not in clisp. In my case, since it's interpreted, there
> definitely are performance advantages. Compare searching one huge
> association list versus searching through a few small ones like a tree
> structure. If it's compiled, then you're right. If not, then I'm
> right.
Common Lisp tries to make all the necessary decisions about
symbols and their packages at readtime, so there is no
runtime penalty.
...
> > > I have eliminated that distinction by allowing function
> > > definitions to dictate which parameters are to be evaluated. For
> > > instance:
> > > (defun func (eval_param 'uneval_param) body)
> > > In this case, uneval_param is quoted, indicating that the parameter in
> > > the function call should be taken literally. This made things like my
> > > internal definitions of DEFINE, DEFUN, QUOTE, and COND more elegant to
> > > implement.
> >
> > That was many years ago common in various Lisp dialects.
> > It has been removed for several reasons. Scheme and
> > Common Lisp don't provide this. There are only a very
> > small amount of built-in and non-extensible special
> > forms. The rest are macros and ordinary function.
>
> Why was it removed? Care to give some reasons?
A good introduction is 'The evolution of Lisp'.
http://www.dreamsongs.com/NewFiles/HOPL2-Uncut.pdf
Mostly it has been decided that there was a clearer model
that allowed to write good compilers more easily.
> Did macros make it
> obsolete? I had two reasons for doing it this way. One is that
> special forms and functions could be unified. The other is that it was
> a step towards unifying those with macros.
This is the Lisp history backwards. You try to introduce a feature
that has been removed in the evolution of Lisp. ;-)
> That all depends on how you define 'namespace', now doesn't it?
> Perhaps I should have chosen a different word, but I think it's quite
> clear from context what I mean.
>
> > You are probably confusing 'namespaces' and 'environments'.
>
> Yes, I think, for some purposes, 'environment' might be clearer, but it
> does not fully embody everything that my 'namespaces' are capable of.
I think it would be better not to redefine concepts that
are already used somewhere else (atleast not
without making the difference clear).
Common Lisp:
Environment
http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_e.htm#environment
Namespace
http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_n.htm#namespace
Package
http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_p.htm#package
>
>
> > > Any suggestions? :)
> >
> > Yes, if you don't have it, run not walk and buy
> > Christian Queinnec's Book 'Lisp' (Lisp in Small Pieces).
>
> I've looked into it, although I do already have some books on Lisp. I
> appreciate the pointer, but I get the feeling that you're pointing me
> at this as a way to tell me that existing Lisps are 'right' and that my
> implementation is wrong.
Lisp has an evolution of more than 40 years. The first Lisp appeared
around 1958. Since then literally thousands of different versions of Lisp
have been implemented. The chance that you discover something totally
new is not that big. Chances are high that many design choices that
you faces have been researched by others already.
> So either I'm a complete idiot and need to be
> educated, or you aren't bothering to think past what you're accustomed
> to and think about alternative ways of representing things. Which is
> it? (Of course, I'm perfectly willing to consider that it's a
> combination of the two or a third option entirely...)
From reading you post I think you don't understand much of Common Lisp,
still you have strong opinions of not wanting some of its features.
Especially some of the features which really do not work the way you
think. I'm not against experimenting with other ideas (I have used
more Lisp dialects than just Scheme and Common Lisp) - but you should
get the basic facts right, so you base your decisions not on hearsay.
Though, experimenting with an implementation (for example by writing one)
is a very good way to get a deeper understanding.