Is there any function to add/remove/update cons cells (which contain
key/value pair) from a alist (association list, i guess) ?
Example :
(setq alist '((name . "John") (age . 21)))
Now i want to modify John´s age. How can i do this ?
-----------------------------------------------------------
| ________ |
| o | __ | Daniel Henrique Alves Lima , RA:970482 |
| _O | |__| | Ciencia da Computacao 97 / Unicamp |
| ____/\ |___WW___| E-mail : 970...@dcc.unicamp.br |
| __/ / || |
| || |
| || |
| ____________||___________________________________________ |
-----------------------------------------------------------
> Hi, everybody.
>
> Is there any function to add/remove/update cons cells (which contain
> key/value pair) from a alist (association list, i guess) ?
>
> Example :
>
> (setq alist '((name . "John") (age . 21)))
>
> Now i want to modify Johnæ„€ age. How can i do this ?
(setf (cdr (assoc 'age alist)) 76)
Note that SETQ is only really useful if you are setting the value of a
symbol, rather than some random address, so you have to use SETF in
this case. Keeping in mind that everything is a pointer, I prefer to
always use SETF for consistency, but it's just a matter of taste (or
so says Paul Graham).
--
vsync
http://quadium.net/ - last updated Fri Oct 27 16:38:34 PDT 2000
(cons (cons (car (cons 'c 'r)) (cdr (cons 'a 'o))) ; Orjner
(cons (cons (car (cons 'n 'c)) (cdr (cons nil 's))) nil))
>
> (setq alist '((name . "John") (age . 21)))
>
> Now i want to modify John=B4s age. How can i do this ?
>
(let ((found (assoc key alist)))
(if found
(setf (cdr found) value)
(cons (cons key value) alist)))
is probably something like what you want.
--tim
Ordinary list manipulation functions apply.
| Example :
|
| (setq alist '((name . "John") (age . 21)))
|
| Now i want to modify Johnæ„€ age. How can i do this ?
How do you look it up to begin with? The general answer is to use
setf with that form to set the new value.
| -----------------------------------------------------------
| | ________ |
| | o | __ | Daniel Henrique Alves Lima , RA:970482 |
| | _O | |__| | Ciencia da Computacao 97 / Unicamp |
| | ____/\ |___WW___| E-mail : 970...@dcc.unicamp.br |
| | __/ / || |
| | || |
| | || |
| | ____________||___________________________________________ |
| -----------------------------------------------------------
Drop this.
#:Erik
--
Does anyone remember where I parked Air Force One?
-- George W. Bush
> Hi, everybody.
>
> Is there any function to add/remove/update cons cells (which contain
> key/value pair) from a alist (association list, i guess) ?
>
> Example :
>
> (setq alist '((name . "John") (age . 21)))
>
> Now i want to modify John´s age. How can i do this ?
The simple answer(s):
a) Destructively:
(setf (cdr (assoc 'age alist)) 42)
You should check that assoc doesn't return nil (in case there is no
entry for 'age in alist) to make this safe.
b) Non-destructivley:
(push (cons age 42) alist)
This will make alist equal to '((age . 42) (name . "John") (age . 21))
and hence shadow the old age entry. (assoc 'age alist) will now
return '(age . 42).
The correct question:
Why would you want to use alists in the first place? It looks like
the alist in question should describe a person. In this case you
should use structures or CLOS objects to hold the data:
(defstruct person
name
age)
(defvar *john* (make-person :name "John" :age 21))
(describe *john*)
(setf (person-age *john*) 42)
(describe *john*)
Regs, Pierre.
--
Pierre R. Mai <pm...@acm.org> http://www.pmsf.de/pmai/
The most likely way for the world to be destroyed, most experts agree,
is by accident. That's where we come in; we're computer professionals.
We cause accidents. -- Nathaniel Borenstein
> Is there any function to add/remove/update cons cells (which contain
> key/value pair) from a alist (association list, i guess) ?
>
> Example :
>
> (setq alist '((name . "John") (age . 21)))
>
> Now i want to modify John's age. How can i do this ?
Well, technically you can't do it here since you made the cell containing
John's age be a literal constant and you don't want to be modifying those.
Presumably you're talking about a cell that's not that way, such as:
(defun make-person-data (name age)
(list (cons 'name name) (cons 'age age)))
(setq alist (make-person-data "John" 21))
The answer then is that there are no prepackaged alist operations that do
this kind of side-effect. Not for any good reason. In most cases, people
use plists for this rather than alists because there ARE prepackaged
operations that you can use.
(defun make-person-data (name age)
(list 'name name 'age age))
(setq person17-alist (make-person-data "John" 21))
(getf person17-alist 'age) => 21
(setf (getf person17-alist 'age) 25) => 25
(incf (getf person17-alist 'age)) => 26
(getf person17-alist 'age) => 26
(defmacro age (person-data-alist) `(getf ,person-data-alist 'age))
(age person17-alist) => 26
It's not obvious why you're not using DEFSTRUCT or DEFCLASS to record this
information, though. That would allow you direct access to the slots
instead of searched access. You should only use this kind of representation
if you have a large number of slots most of which are missing most of the
time, or if you have a cascaded inheritance system that depends on slots of
the same name shadowing other deeper slots, or something like that.
Good luck.
p.s. I hope this wasn't a homework problem, but fortunately the "right"
answer to this doesn't involve solving the problem as stated, so I won't
have revealed any homework answers.
> DANIEL HENRIQUE ALVES LIMA <970...@tigre.dcc.unicamp.br> writes:
>
> > Hi, everybody.
> >
> > Is there any function to add/remove/update cons cells (which contain
> > key/value pair) from a alist (association list, i guess) ?
> >
> > Example :
> >
> > (setq alist '((name . "John") (age . 21)))
> >
> > Now i want to modify John´s age. How can i do this ?
>
> (setf (cdr (assoc 'age alist)) 76)
Mostly I recommend not doing this unless you've first verified, either
statically or dynamically, as appropriate, that the ASSOC call is reliably
not going to return NIL.
> Hi, everybody.
>
> Is there any function to add/remove/update cons cells (which contain
> key/value pair) from a alist (association list, i guess) ?
>
> Example :
>
> (setq alist '((name . "John") (age . 21)))
>
> Now i want to modify John=B4s age. How can i do this ?
(rplacd (assoc 'age alist) 25)
or
(setf (cdr (assoc 'age alist)) 25)
--
Nils Goesche
"Don't ask for whom the <CTRL-G> tolls."
This solution has the additional benefit of making various kinds of
rollback or undo schemes fairly easy to implmement. If one were
interested in being able to do that, then the additional storage space
and search-based access may be worth the cost.
--
Thomas A. Russ, USC/Information Sciences Institute t...@isi.edu
And in the else branch, you probably want to set alist to the new
list, or more directly:
(push (cons key value) alist)))
Setting or adding a value to an alist is common enough that LispWorks
has had a feature for the purpose for some time, although it's not
been documented:
(setf (sys:cdr-assoc key alist) value)
Also available in the latest Liquid releases.
--
Pekka P. Pirinen, Adaptive Memory Management Group, Harlequin Limited
A programming language is low level when its programs require attention to
the irrelevant. - Alan Perlis
> Hi, everybody.
>
> Is there any function to add/remove/update cons cells (which contain
> key/value pair) from a alist (association list, i guess) ?
>
> Example :
>
> (setq alist '((name . "John") (age . 21)))
>
> Now i want to modify John=B4s age. How can i do this ?
>
Use DEFSTRUCT. I.e. use the best available data type for your needs.
(defstruct person name age)
(setf p (make-person :name "John" :age 24)
(setf (person-age p) 29)
Of course you cannot do that in Scheme (note: not in R^xRS ==> not in Scheme),
but you can do it in Elisp (just stick "(require 'cl)" in your
.emacs).
Cheers
--
Marco Antoniotti =============================================================
NYU Bioinformatics Group tel. +1 - 212 - 998 3488
719 Broadway 12th Floor fax +1 - 212 - 995 4122
New York, NY 10003, USA http://galt.mrl.nyu.edu/valis
Like DNA, such a language [Lisp] does not go out of style.
Paul Graham, ANSI Common Lisp
> Use DEFSTRUCT. I.e. use the best available data type for your needs.
What's the point of using defstruct when we have classes in the
standard? A struct is like an optimized object with certain restrictions
(no change-class etc.). If someone wants the make-* functions, there
could be an (initialize-instance :after ((class standard-class)...)...)
or some macro.
Robert
Am I confused, or are structs not inherently more efficient than standard
class (due to the business with being required to detect slot-unbound in
standard-class)? It's been a whlie since I looked so my memory may be foggy
on this--I do know there's an inefficiency in the slot access somewhere that
really bugs me, and I think it's the unbound check; I'd have required it only
in safe code...
Also, the defstruct syntax offers many advantages of brevity over
standard-class, and I often use it when prototyping.
I sometimes also use defstruct to signal that my reliance on CLOS more
complex features is minimal. Sometimes I even use it to prevent myself
from using those features in code I plan to translate to another language
for deployment.
So I would not say it's pointless to use defstruct.
Well, you answered your own question. Structures are exceptionally
efficient in Allegro CL, which open-codes all slot accessors with
the proper declarations. I use them for time-critical stuff that I
have tuned and tested and played with using classes and the class
slot accessors are measurably too expensive.
> Am I confused, or are structs not inherently more efficient than standard
> class (due to the business with being required to detect slot-unbound in
> standard-class)? It's been a whlie since I looked so my memory may be
> foggy
> on this--I do know there's an inefficiency in the slot access somewhere
> that
> really bugs me, and I think it's the unbound check; I'd have required it
> only
> in safe code...
Doesn't CLOS have some equivalent to Dylan's "required-init-keyword",
"init-value" or "init-function" slot options? If you use any of those
then Gwydion d2c (and I assume Functional Developer, but I haven't seem
either the source code or the output of it) knows it can omit unbound
slot checks because the slot *will* have a value.
-- Bruce
Yes, it does. It also has SLOT-MAKUNBOUND...
--tim
> Doesn't CLOS have some equivalent to Dylan's "required-init-keyword",
> "init-value" or "init-function" slot options? If you use any of those
> then Gwydion d2c (and I assume Functional Developer, but I haven't seem
> either the source code or the output of it) knows it can omit unbound
> slot checks because the slot *will* have a value.
Since CLOS also provides the SLOT-MAKUNBOUND function, the checks
can't be omitted.
Just like defun hasn't been obsoleted by defgeneric, defstruct isn't
made obsolete by defclass. Apart from the differences in overhead, I
use defun and destruct in areas where I consciously didn't provide for
full genericity. This documents those restrictions, while not
precluding later changes to defgeneric and/or defclass.
But those changes can't be made wantonly: Just because you use
defgeneric/defclass doesn't mean that you have designed the protocols
those define to correctly support sensible genericity. By using
defun/defstruct I indicate to future programmers that the interfaces
have not been designed for genericity, and should therefore be
properly checked in case genericity is wanted later on.
Just to add to Kent's points....
(defstruct zut a b c)
(defgeneric I-work-with-structs (x)
(:method ((x zut)) (values (zut-a x) (zut-b x) (zut-c x))))
So, if you do not need the full blown power of CLOS for data
representation (including inheritance), the generic function machinery
allows you to use all the method facilities (:before, :after etc etc)
without using classes.
> (defstruct zut a b c)
>
> (defgeneric I-work-with-structs (x)
> (:method ((x zut)) (values (zut-a x) (zut-b x) (zut-c x))))
>
> So, if you do not need the full blown power of CLOS for data
> representation (including inheritance), the generic function machinery
> allows you to use all the method facilities (:before, :after etc etc)
> without using classes.
Just curious -- why would one want to use :before and :after methods
if there is no inheritance?
Janis Dzerins
--
Ever feel like life was a game and you had the wrong instruction book?
> (defstruct zut a b c)
>
> (defgeneric I-work-with-structs (x)
> (:method ((x zut)) (values (zut-a x) (zut-b x) (zut-c x))))
>
And, of course, structures also have single-inheritance. So actually
there's aenough there to do a lot of things.
--tim
>
> Just curious -- why would one want to use :before and :after methods
> if there is no inheritance?
>
There is (single) inheritance.
--tim
You forgot at least the inheritance from T.
To follow Marco's example:
(defmethod i-work-with-structs :after (x)(print :after))
(defmethod i-work-with-structs :after ((x zut))(print :after-zut))
INTERFACE 112 > (I-work-with-structs (make-zut))
:after
:after-zut
nil
nil
nil
Marc Battyani
> Marco Antoniotti <mar...@cs.nyu.edu> writes:
>
> > (defstruct zut a b c)
> >
> > (defgeneric I-work-with-structs (x)
> > (:method ((x zut)) (values (zut-a x) (zut-b x) (zut-c x))))
> >
> > So, if you do not need the full blown power of CLOS for data
> > representation (including inheritance), the generic function machinery
> > allows you to use all the method facilities (:before, :after etc etc)
> > without using classes.
>
> Just curious -- why would one want to use :before and :after methods
> if there is no inheritance?
Maybe as a code organization method. Imagine that
you have a library of code and somewhere else you
want to modify existing behavior without touching
the primary method. This for example is a topic when
you want to ship patches for your software. For normal
functions some Lisp systems have a mechanism
called "advise".
Here is an example for Macintosh Common Lisp:
; only an example, nothing clever
(defun do-some-long-computation (foo)
(+ foo 3109283109))
; later
(advise do-some-long-computation
(unless (y-or-n-dialog "Really start long computation?")
(cancel))
:when :before
:name :ask-user-before-doing-some-long-computation)
; even later
(advise do-some-long-computation
(unless (y-or-n-dialog "Really really start long computation?")
(cancel))
:when :before
:name :ask-user-before-doing-some-long-computation-1)
ADVISE can add to existing functions "before", "after" and
"around" behavior. In MCL advise can add code more
than once - in CLOS there is usually only one :BEFORE/:AFTER/
:AROUND method for a certain parameter list.
The same with methods:
(defmethod do-some-long-computation (foo)
(+ foo 3109283109))
; later
(defmethod do-some-long-computation :before (foo)
(declare (ignore foo))
(unless (y-or-n-dialog "Really start long computation?")
(cancel)))
; even later, overriding the first version
(defmethod do-some-long-computation :before (foo)
(declare (ignore foo))
(unless (y-or-n-dialog "Really start long computation?")
(cancel))
(unless (y-or-n-dialog "Really really start long computation?")
(cancel)))
(Btw., structures do support single inheritance.)
--
Rainer Joswig, Hamburg, Germany
Email: mailto:jos...@corporate-world.lisp.de
Web: http://corporate-world.lisp.de/
[I assume this is just a correction and you do not mean that method
combination is useful for structs.]
Of course one can (as opposed to should) use methods for structs, but
I see _no_ use for [standard] method combination when there is only
single inheritance. Hence the question -- can anyone provide a
scenario where doing this makes sense?
> Just curious -- why would one want to use :before and :after methods
> if there is no inheritance?
Once in a while the answer is felicity... you find that someone else has
defined the primary and you don't have access to the source but want to
add some behavior. This is a sort of limited variant of a defadvice kind
of thing, but works in portable code.
Also, sometimes you maintain the code yourself and have an optional module
that wants to add extra behavior. In other words, you're both the person
who developed the original code and the client who wants to extend it without
modifying the original code.
And sometimes it just "feels right" expressionwise. The same reason why
sometimes when writes (when (not ...) ...) and other times writes (unless ...).
They say the same thing, but sometimes one feels better than the other for
reasons that can't be easily articulated as a "general rule".
Not to mention, though others have, that there is some limited inheritance
in play in the original scenario that the above query was responding to.
But I just wanted to answer the query on its own terms.
>
> [I assume this is just a correction and you do not mean that method
> combination is useful for structs.]
No, I think it's useful for structs (and for built-in classes too).
>
> Of course one can (as opposed to should) use methods for structs, but
> I see _no_ use for [standard] method combination when there is only
> single inheritance. Hence the question -- can anyone provide a
> scenario where doing this makes sense?
I can think of plenty of uses. Imagine I have two (structure) classes
a and b of which b is a subclass of a. I have some operation I do on
a, and when I do it on bs I need to have a lock on something.
(defmethod op ((ob a ) ...) ...)
(defmethod op :around ((ob b) ...)
(with-lock ...
(call-next-method)))
or I have a class a and then a subclass logged-a which is identical to
a except I want to log things:
(defmethod op ((ob a) ...) ...)
(defmethod op :after ((ob logged-a) ...)
(log-info 'op ...))
And lots of other cases.
--tim
I find it very useful even when I have unrelated (except for T) classes.
For me the implied inheritance from T is very handy. (As I've written in my
previous post)
I've written a 3D ultrasound OpenGL simulation where I have to display
unrelated objects like CAD parts, ultrasound beams etc. In this case I had
general around methods for several generic functions related to OpenGL.
(acquire/release a lock, setup 3D parameters, etc.). I have not used a mixin
class because I wanted the OpenGL stuff to be separated from the simulation
stuff.
There are other ways to do it. As usual Lisp provides several ways to do
things.
BTW I didn't speak about it, but single inheritance is very useful too. I
just give this example to show you that the around, after, and before
methods are useful even with no inheritance.
Marc Battyani
> Tim Bradshaw <t...@tfeb.org> writes:
>
> > Janis Dzerins <jo...@latnet.lv> writes:
> > >
> > > Just curious -- why would one want to use :before and :after methods
> > > if there is no inheritance?
> > >
> >
> > There is (single) inheritance.
>
> [I assume this is just a correction and you do not mean that method
> combination is useful for structs.]
>
> Of course one can (as opposed to should) use methods for structs,
> but
That was exactly my point. You can! Not necessarily you must.
> I see _no_ use for [standard] method combination when there is only
> single inheritance.
Do you mean that you never use :before and :after method on objects
defined as
(defclass zut (zot) ....)
Think of accessor methods. They are very good candidates.
> Hence the question -- can anyone provide a scenario where doing this
> makes sense?
I am sure somebody else will.
> Of course one can (as opposed to should) use methods for structs, but
> I see _no_ use for [standard] method combination when there is only
> single inheritance. Hence the question -- can anyone provide a
> scenario where doing this makes sense?
From
ftp://samaris.tunes.org/pub/food/papers/people/Henry.Baker/hbaker/CritLisp.html
In fact, we have built [Baker91CLOS] a single-inheritance
mini-CLOS with generic functions, using defstruct instead
of defclass to define "things with slots". There are
significant efficiency advantages to this approach, since
one is not saddled with the whole "metaobject protocol"
[Bobrow88] [desRiviires90], as well as with the full
complexity of defclass. Furthermore, since defstruct can
already incrementally extend an existing structure using
single inheritance, and since the real power of
object-oriented programming resides in generic functions,
we gain in simplicity, while losing almost nothing in
power.
If it's good enough for Baker, _I'm_ not going to argue.
- David
--
David J. Fiander | We know for certain only when we know little.
Librarian | With knowlege, doubt increases
| - Goethe
> Janis Dzerins <jo...@latnet.lv> writes:
>
> > Of course one can (as opposed to should) use methods for structs, but
> > I see _no_ use for [standard] method combination when there is only
> > single inheritance. Hence the question -- can anyone provide a
> > scenario where doing this makes sense?
>
> From
> ftp://samaris.tunes.org/pub/food/papers/people/Henry.Baker/hbaker/CritLisp.html
>
> In fact, we have built [Baker91CLOS] a single-inheritance
> mini-CLOS with generic functions, using defstruct instead
> of defclass to define "things with slots". There are
> significant efficiency advantages to this approach, since
> one is not saddled with the whole "metaobject protocol"
> [Bobrow88] [desRiviires90], as well as with the full
> complexity of defclass. Furthermore, since defstruct can
> already incrementally extend an existing structure using
> single inheritance, and since the real power of
> object-oriented programming resides in generic functions,
> we gain in simplicity, while losing almost nothing in
> power.
Which brings us to another very interesting paper:
Harry Bretthauer
Entwurf und Implementierung effizienter Objektsysteme
für funktionale und imperative
Programmiersprachen am Beispiel von Lisp
1999, 353 pages
http://www.gmd.de/publications/research/1999/004/
You can download a PDF version via the above link. It
is in german though. Time to brush up your german
language skills. ;-)
Abstract
Up to now a gap is evident in object systems of functional and
procedural programming languages. The most expressive object
system developed in the family of functional languages is
CLOS with its outstanding metaobject protocol. Its performance,
however, does not meet the users' needs. In the family of
procedural languages the most efficient object system developed
is C++. But its support of central concepts of object-oriented
programming, such as specialization and generalization of
object classes, is not sufficient. This also applies in some
degree for Java .
Using Lisp as an example this thesis shows how efficient object
systems can be designed and implemented so that simple
constructs have no overhead because of the presence of complex
concepts such as the metaobject protocol or the redefinition of
classes. In contrast to former assumptions, this thesis proofs
for the first time that the above mentioned concepts can be
realized without embedding an interpreter or an incremental
compiler in the run-time environment. Therefore, they can also
be supported in traditional compiler-oriented programming
languages such as Ada, Pascal, Eiffel, C ++ , and Java .
Keywords
object systems, concepts of object-oriented programming,
specialization, generalization, metaobject protocol,
redefinition of classes, functional and procedural programming
languages, performance, interpreter, incremental compiler.
Kurzfassung
Bisherige Objektsysteme funktionaler und imperativer
Programmiersprachen weisen eine Lücke auf. Aus der funktionalen
Tradition wurde das ausdrucksstärkste Objektsystem CLOS
entwickelt, das insbesondere durch sein Metaobjektprotokoll
hervorsticht, dessen Performanz aber zu wünschen übrig läßt.
Auf der anderen Seite zeichnet sich C++ als besonders effizient
aus, unterstützt aber zentrale Konzepte objektorientierter
Programmierung wie Spezialisieren und Generalisieren von
Objektklassen nur unzureichend, was abgeschwächt auch für Java
gilt.
In dieser Arbeit wird am Beispiel von Lisp gezeigt, wie man
effiziente Objektsysteme unter Berücksichtigung des
Verursacherprinzips so entwirft und implementiert, daß einfache
Konstrukte keinen Overhead durch die Präsenz aufwendiger
Konzepte, wie des Metaobjektprotokolls oder des Redefinierens
von Klassen, mittragen müssen. Entgegen bisherigen Annahmen
wird hier erstmals nachgewiesen, daß diese Konzepte auch ohne
Quellcodeinterpretation bzw. -kompilation zur Laufzeit
realisiert und somit auch in traditionellen,
compiler-orientierten Programmiersprachen, wie Ada, Pascal,
Eiffel, C ++ und natürlich Java , unterstützt werden können.
Schlagworte
Objektsystem, Konzepte objektorientierter Programmierung,
Spezialisieren, Generalisieren, Metaobjektprotokoll,
Redefinieren von Klassen, funktionale und imperative
Programmiersprachen, Performanz, Quellcodeinterpretation,
Kompilation zur Laufzeit.
> can anyone provide a scenario where doing [method combination when
> there is only single inheritance] makes sense?
For example, I use it to separate business logic, type validation and
caching. The main function is the business logic in its possibly simple
form. A short :around method would look up a hash table for cached
results to see if (call-next-method) can be avoided. A :before method
would check argument types (for certain classes). Maybe an :after
method (to a slot writer, for example) would invalidate dependent cached
values.
Robert
> > What's the point of using defstruct when we have classes in the
> > standard?
> Am I confused, or are structs not inherently more efficient than
> standard class (due to the business with being required to detect
> slot-unbound in standard-class)?
They are now, but it should not necessarily be that way. There is a
*huge* functional overlap between structs and classes, with some
differences:
- Structs are more efficient.
-> There is no reason why operations on CLOS objects could not be as
fast as structs. A lot of CL fuctions make good use of declarations
(arithmetics, iterations, sequence operations, ...) - why is
CLOS the exception? For example, compilers don't give a damn
about the slot type declarations. I believe fast, unsafe code
should take the declared type granted, while safe code should signal
an error. Your idea that slots could be declared always bound is
also realized this way. Why is the type declaration there, just for
documentation? Some implementations don't make method calls
efficient, even if there is only one method for a GF.
You could declare things like class sealing, in addition.
- Structs are "just" different (e.g., they generate make- functions)
-> This is historical baggage. If automagical function generation or
struct representation specification is useful for structs, they
should be useful for classes, too. Are there any qualities of
structs that could not be transported to CLOS? E.g., make-
functions
are pure macrology.
I have read all answers (thanks!) and remain convinced that structs
should be deprecated, being used as legacy or performance enhancements
(for which declarations are invented).
Robert
> Just like defun hasn't been obsoleted by defgeneric, defstruct isn't
> made obsolete by defclass.
I have a similar opinion for defun vs. defmethod, so I cannot accept it
as an argument :-)
> Apart from the differences in overhead, I
> use defun and destruct in areas where I consciously didn't provide for
> full genericity. This documents those restrictions
Does it? The defun's function parameters are not specialized. If one
of the arguments is called "loan", then you would think that the
function will correctly handle an "equity loan", too. If it is not the
case, DEFUN is not a good way to signal it -assert is better. Which of
course could be equally used in a method. There are even better ways of
signaling intent - specialize a method for equity loans, and use
(defmethod default-risk ((loan home-loan) date)
(break "Equity loans will be supported in r3.0"))
Or even _name_ it in an ungeneric way temporarily:
(defmethod regular-loan-default-risk ((loan loan) date)
...)
> Just because you use
> defgeneric/defclass doesn't mean that you have designed the protocols
> those define to correctly support sensible genericity.
As seen above, the use of DEFUN does not imply the lack of intent for
genericity, to say the least. I think that the best way of implying
non-genericity is using an obviously non-generic function name. Even
then it is a rare or temporary measure.
You argue for an arbitrary and ambiguous expression of intent: methods
can be specialized for both structs and standard-classes, and structs
also support inheritance. Seeing a struct, I would think it is legacy,
or sign of being more accustomed to slots, or performance hack, or
bootstrapping for user-level CLOS implementations (in this order).
Thanks for your comments,
Robert
> - Structs are more efficient.
> -> There is no reason why operations on CLOS objects could not be as
> fast as structs. A lot of CL fuctions make good use of declarations
> (arithmetics, iterations, sequence operations, ...) - why is
> CLOS the exception? For example, compilers don't give a damn
> about the slot type declarations. I believe fast, unsafe code
> should take the declared type granted, while safe code should signal
> an error. Your idea that slots could be declared always bound is
> also realized this way. Why is the type declaration there, just for
> documentation? Some implementations don't make method calls
> efficient, even if there is only one method for a GF.
> You could declare things like class sealing, in addition.
>
> - Structs are "just" different (e.g., they generate make- functions)
> -> This is historical baggage. If automagical function generation or
> struct representation specification is useful for structs, they
> should be useful for classes, too. Are there any qualities of
> structs that could not be transported to CLOS? E.g., make-
> functions
> are pure macrology.
>
> I have read all answers (thanks!) and remain convinced that structs
> should be deprecated, being used as legacy or performance enhancements
> (for which declarations are invented).
Problems mentioned in the paper by Bretthauer:
a) MAKE-INSTANCE is expensive.
Call sequence with potentially expensive argument processing:
MAKE-INSTANCE
MAKE-INSTANCE (FIND-CLASS)
ALLOCATE-INSTANCE
INITIALIZE-INSTANCE
SHARED-INITIALIZE
b) slot access is "slow".
Call sequence:
READER
SLOT-VALUE
SLOT-VALUE-USING-CLASS (CLASS-OF, FIND-SLOT)
Slot access via simple vector reference is
not possible (? -> multiple inheritance, class redefinition,
change class, ...).
...
Well, I'd like to see some standard mechanisms to speed up
CLOS and to be able to get rid of structures eventually.
Various implementations are supporting precaching, primary
classes, etc.
A benchmark suite with the emphasis on
CLOS speed (functions vs. generic functions,
classes vs. structures vs. vectors, ...) would surely
help to make the various performance characteristics
more transparent. This would help advocating for
improvements and is necessary as a first step, so that
we know about what performance problems we are
really talking about.
> > Apart from the differences in overhead, I
> > use defun and destruct in areas where I consciously didn't provide for
> > full genericity. This documents those restrictions
>
> Does it? The defun's function parameters are not specialized. If one
> of the arguments is called "loan", then you would think that the
> function will correctly handle an "equity loan", too. If it is not the
> case, DEFUN is not a good way to signal it -assert is better. Which of
> course could be equally used in a method. There are even better ways of
> signaling intent - specialize a method for equity loans, and use
>
> (defmethod default-risk ((loan home-loan) date)
> (break "Equity loans will be supported in r3.0"))
You have misunderstood my term "full genericity", or more likely I
haven't used the correct term. I didn't mean that I use defun to
document that certain functions don't work for certain _known_
arguments. That would be stupid, and using assert or check-type,
as well as explicit documentation would be the way to go. In fact
the issue of input type specification is totally disjoint from the
issue of extensible GFs: As we all know + works for all numbers, so
it is generic. But it is not arbitrarily extensible, and for good
reasons: There is no documented protocol for extending +, i.e. do you
also need to extend -,/,*,..., what invariants (not only algebraic
invariants!) must be met, etc.
What I meant was that for me the set of all generic functions in a
certain part of an application are part of a protocol, that implicitly
and explicitly (through support documentation) defines an interface
for extension. It tells the programmer: These are interfaces that
have been designed for extension into certain directions, so if you
want, you can just write a couple of methods on these, and thereby
extend the basic machinery to support e.g. a new disjoint class, and I
as the original author guarantee that this will work as intended.
OTOH if I write something _without_ having designed a _consistent_
protocol for extension, then I don't use defgeneric, because I can't
(and don't want to) guarantee that you can indeed write different
methods on these, without having to grok or change something of the
underlying machinery.
So it is not the case that I use defun to prohibit _known_
extensions/uses (which would be stupid), but to indicate that
extensibility was not expected here, and so the programmer that wants
to extend it will know that he has to go over all the related code and
make sure he can indeed extend it in the way he wants, and on the way
design a suitable extension protocol.
Take a look at the MOP for an example: In areas that are clearly
_designed_ for extension, you will find generic functions. In areas
that are clearly not intended for extension, you will find defuns.
If you take the stance that defun should be deprecated, then you'd
presumably want e.g. car to be a gf, too. Would that really be a
sensible thing to do? Would it not invite the extension of car with
new methods? What invariants would be broken if car worked on, let's
say structures? Has anybody sat down and designed a protocol for car
extension? What other GFs will have to be overriden, so that no
(at the moment undocumented) system invariants will be broken?
Every GF is (part of) an (internal) _interface_ that indicates that as
long as you follow any additional constraints set out in the
documentation of the interface, you are free to extend the GF with
your own methods and expect it to work reliably. You can expect that
the original author has already thought of all the implications of
extending this GF, and has taken those into account in his code.
Furthermore, IMHO, you should be able to expect that the GF, being an
interface, will not be changed, renamed, or removed without at least
some hesitation in future versions[1], since not only callers but also
GF implementors will depend on the exact semantics of the GF.
E.g. I think it is perfectly acceptable for some function foo to be
called each time something happens in one version, and then only the
first time something happens in a new version, without anyone getting
notified about it. For a GF, I'd hesitate making this change, and I'd
make sure that anyone depending on the exact semantics of the GF will
get notified.
Just give another example: As part of our simulation toolkit, I've
implemented a generic priority queue implementation. Of course I've
used CLOS to give an extensible interface for priority-queues:
(defclass priority-queue ()
((predicate :initarg :predicate :reader priority-queue-predicate
:type function :initform #'<
:documentation
"Strictly monotonous binary predicate on queue items.")))
...
(defgeneric priority-queue-insert (queue item)
(:documentation "Insert a new item into the priority queue."))
(defgeneric priority-queue-extract-min (queue)
(:documentation
"Removes and returns the minimal item from the priority queue."))
...
OTOH the different implementations (naive linear lists, splay trees,
skew heaps, binomial heaps...) don't use GFs or classes internally,
because it IMHO doesn't make sense: (To Me) There is no useful way in
which the methods of a basic skew heap implementation should be
extended via method definition. The work required to define useful
(for what?) interfaces for this would make the implementation totally
unreadable, bloated and slow, without giving any benefits, since you'd
rewrite the stuff much more easily than try to understand the now
bloated implementation, and extend it. It's much easier to wrap it
instead:
;;; Skew Heaps
;;; Internals
(defstruct (skew-heap
(:constructor make-skew-heap (item &optional left right)))
(left nil :type (or skew-heap null))
(right nil :type (or skew-heap null))
item)
...
(defun nmerge-skew-heap (node1 node2 predicate)
(declare (type (or skew-heap null) node1 node2)
(type function predicate))
(cond
((null node1) node2)
((null node2) node1)
((funcall predicate (skew-heap-item node2) (skew-heap-item node1))
;; node2 < node1
(psetf (skew-heap-left node2) (nmerge-skew-heap node1
(skew-heap-right node2)
predicate)
(skew-heap-right node2) (skew-heap-left node2))
node2)
(t
;; node1 <= node2
(psetf (skew-heap-left node1) (nmerge-skew-heap (skew-heap-right node1)
node2
predicate)
(skew-heap-right node1) (skew-heap-left node1))
node1)))
...
(defclass skew-heap-priority-queue (priority-queue)
((heap :initform nil :type (or skew-heap null))))
...
(defmethod priority-queue-insert ((queue skew-heap-priority-queue) item)
(setf (slot-value queue 'heap)
(insert-skew-heap (slot-value queue 'heap) item
(priority-queue-predicate queue))))
...
> You argue for an arbitrary and ambiguous expression of intent: methods
> can be specialized for both structs and standard-classes, and structs
> also support inheritance. Seeing a struct, I would think it is legacy,
> or sign of being more accustomed to slots, or performance hack, or
> bootstrapping for user-level CLOS implementations (in this order).
Well you are free to think that, but I'd claim you'd be wrong at least
a third of the time. It's not that I'm the only one using this
implicit convention (see available source code on the net for largish
systems for generous examples). But that doesn't matter anyway:
Regardless of what you think (and if it's only: That's code by some
CLOS-hating retard), the effect will be the same: You will tread
carefully, and not assume the code was written with arbitrary
extension in mind, without doing a careful review first. And that's
exactly my intent, so I'm happy. ;)
Regs, Pierre.
Footnotes:
[1] The severity of the hesitation depending on whether the GF is
part of the external interface, some internal interface or only
some implementational interface.
You guys seem to forget that structures can be used to describe the
data in lists and vectors, too, and that this is sometimes useful.
I'm sure you think that's to be deprecated, as well, but I'd call on
people to start looking for babies in all the bathwater that is
being thrown out around here. If you use structures as weak or
small objects, hey, sure, no wonder you come to the conclusion that
they are just like classes, only a little less so, but that is not
_all_ that structures are about. There is overlap in functionality,
but if you ignore what is not overlapping while you hype your desire
to deprecate them, which is worse of someone who is disregarding
those other things and needs on purpose and somone who argues very
well for deprecating something that he does not fully appreciate?
If it weren't for a particularly bad choice of Presidents of the
United States looming over us all, I'd say that one should throw out
of the discussion people who display a disdain for the legitimate
positions of their opponents, because their conclusions will have
built in a disdain for _some_ needs, and that invariable means some
_future_ needs.
I implore you all to consider what it would take to change your mind
on whatever it is you believe. Methodologically, it has no value to
have yet another affirmation of some claim if no counter-claims have
been allowed. Some defenders of the faith (because that is what it
becomes) believe that counter-information, counter-views, etc, are
"subversive" and "dangerous" and should be controlled or stopped,
but as soon as you do, the first you lose is credibility: Bystanders
and freethinkers within alike _must_ believe that _you_ have reason
to believe that what you defend is not true or at least not the best
of what is true. Sadly, many people value agreement with some
position over its credibility, as if they _want_ to believe in that
position specifically more than believe in whatever is good and true.
The lack of consideration for what structures do that classes do not
is perhaps the best argument yet for belaying the order to deprecate.
> - Structs are more efficient.
> -> There is no reason why operations on CLOS objects could not be as
> fast as structs. A lot of CL fuctions make good use of declarations
> (arithmetics, iterations, sequence operations, ...) - why is
> CLOS the exception?
The declarations don't exist now. You're talking about a hypothetical
language. Certainly one can redesign the language in a way such that
structs are not needed, but they are needed in the current design.
> For example, compilers don't give a damn
> about the slot type declarations.
This is an argument about implementations, not about the language. It is
entirely irrelevant here. Besides, it is probably not true of CMU CL,
which was offered really as an existance proof by its authors that the
CL language spec was more poweful than it is often implemented. Even so,
I strongly doubt that, other than by block compilation, the CMU CL
implementation can get past the problem of unboundness, even with lots of
type optimization. This is not a type problem.
> I believe fast, unsafe code
> should take the declared type granted, while safe code should signal
> an error. Your idea that slots could be declared always bound is
> also realized this way.
In some imaginary universe where the meaning of type declaration is different.
The whole point of type declarations in the language is to say what the shape
of the container is, not to address the issue of what value something takes
on if the container has not yet been assigned.
Even in strongly typed languages like Java, type declarations can be abused
for unbound types. null is not actually of the type that one is forever
declaring things. There is no material difference here.
> Why is the type declaration there, just for documentation?
It is there for reasons other than you're trying to twist it to.
> - Structs are "just" different (e.g., they generate make- functions)
> -> This is historical baggage.
See my paper on rapid prototyping in Lisp Pointers for why this is more than
you say. The presence of "ready-made" stuff contributes to rapid prototyping
and is not to be discounted.
> I have read all answers (thanks!) and remain convinced that structs
> should be deprecated, being used as legacy or performance enhancements
> (for which declarations are invented).
Convinced, perhaps, but wrong.
Deprecation, which you suggest, is an administrative operation performed on
a piece of fixed text, to say that the language as written is such that there
is not further need to continue use of a particular part of the language, and
users should evacuate use of it prior to the next rev of the language. Since
there is nothing into which people can evacuate many present uses of defstruct,
it is plain that structs cannot be deprecated.
You say to solve this problem with declarations, but there are no declarations
extant that declare the things necessary to eliminate the need for some
uses of DEFSTRUCT. Even the anti-DEFSTRUCT people on the X3J13 committee
acknowledged this, which is why DEFSTRUCT is still present.
Certainly it was everyone's plan to make DEFSTRUCT more integrated if we'd had
more time and dollars, but the world did not work out that way. At minimum,
some relation between DEFSTRUCT and STRUCTURE-CLASS such that one could use
STRUCTURE-CLASS as a metaclass in a DEFCLASS would have been nice, though we
did not get to the point of doing that and it's not guaranteed to work.
Probably you just did not understand the use of the word deprecate, but you are
by your misuse inviting considerable ire among the people who have lingering
legitimate needs for use.
If you're saying you think it is simple to redesign the language and get a
large community to buy into the redesign, then you are also risking people's
ire because some of us have lived that process for decades and know that it's
more complex than you let on there, too.
If what you're saying is that you would make a good world dictator, that may
be the most defensible point you have to make. But it would be subject to
a counterargument of the form "So would I." from nearly any of us. Almost
any one of us could design a better and more coherent language than CL is.
But CL is not an exercise in having one of us go off and design a language
for the others to use, it is an exercise in diplomacy and group compromise.
And the result isn't bad.
But I think you are simply technically wrong in your claim that structs could
be deprecated, and I observe that even the '-if-not' operations [if i remember
right], which are legitimately deprecated under the meta-rules I described,
have caused an outcry from the community who have advanced legitimate
arguments as to philosophical reasons why find-if-not and friends is
important even though it can be simulated a billion other ways. I can make
technical arguments all day, but I am free already not to use them. If my
removing them or even deprecating them causes pain to another person and risks
the shrinking of the size of the community for someone feeling left out, it is
not a good trade.
One of the hardest things that came of the CL experience for me and the other
people I worked with was this understanding of "another's pain". I have
often said (and you are the most recent example) that the very hardest thing
of all in standards work is to take two people, one of whom says "I need x"
and the other of whom says "I don't need x, but it would cost me nothing to
have it" and to get the second person to agree to tolerate x just for the sake
of the loss of the other person's pain. An example of this was FORCE-OUTPUT
in the I/O domain. Some implementations have no buffered I/O and so didn't
want things like this, which would have been simple no-ops in their language.
It offended them to see it, but it was essential to someone else. I have
learned to have considerable disdain for people who can't or won't get inside
the head of another and really try to understand the world from their point
of view, and who instead insist that because they have identified a particular
point of view which they think (without proof) works for them, that it must
be a habitable space for all.
> - Structs are more efficient.
> -> There is no reason why operations on CLOS objects could not be as
> fast as structs. A lot of CL fuctions make good use of declarations
> (arithmetics, iterations, sequence operations, ...) - why is
> CLOS the exception? For example, compilers don't give a damn
> about the slot type declarations. I believe fast, unsafe code
> should take the declared type granted, while safe code should signal
> an error. Your idea that slots could be declared always bound is
> also realized this way. Why is the type declaration there, just for
> documentation? Some implementations don't make method calls
> efficient, even if there is only one method for a GF.
> You could declare things like class sealing, in addition.
I think there are reasons why it's hard to make CLOS *without
restrictive assumptions* as fast as structures. The obvious one is
redefinition -- if you can always redefine a class then it is hard to
really aggressively compile slot-access -- it's almost certainly not
safe to compile it to something like an array reference, while it *is*
OK to do that for structures.
And of course the MOP gets in the way, but I'll ignore that.
Note I said `without restrictive assumptions' -- I don't mean by this
that restrictive assumptions might not be made -- something like
sealing declarations could solve a lot of the redefinition issues.
Dylan has these.
Of course the poor resource-starved vendor then is faced with having
to still support the full CLOS behaviour, or lots of code will
break. but also to try and implement new fast behaviour when
declarations where in place. I suspect that in many cases people
would just elect not to do the optimisations (which would be perfectly
fine).
And finally, why bother with all this? Structures already allow you
to make many assumptions you need -- no redefinition, single
inheritance & hence known slot positions &c &c...
--tim
Some of the declarations exist now (especially object type declarations
and slot type declarations). Still, preferring structs over CLOS solely
for high speed is inherently an implementation-specific decision, so we
would not be worse off even with implementation-dependent declarations.
> > I believe fast, unsafe code
> > should take the declared type granted, while safe code should
> > signal an error. Your idea that slots could be declared always
> > bound is also realized this way.
> In some imaginary universe where the meaning of type declaration is
> different. The whole point of type declarations in the language is to
> say what the shape of the container is, not to address the issue of
> what value something takes on if the container has not yet been
> assigned.
...
> > Why is the type declaration there, just for documentation?
>
> It is there for reasons other than you're trying to twist it to.
Quoting from your excellent Hyperspec:
"The :type slot option specifies that the contents of the slot will
always be of the specified data type. It effectively declares the result
type of the reader generic function when applied to an object of this
class. The consequences of attempting to store in a slot a value that
does not satisfy the type of the slot are undefined."
Does this not mean that the slot contents could be assumed to be of a
certain type by unsafe code? Does not this mean that the implementor is
allowed to either require an :initarg or have the slot take on an
undefined value (as customary with float arrays) in unsafe code?
In any case, it's nit-picking, because the standard does not say that
structs must be implemented efficiently, and it does not prohibit CLOS
from respecting declarations or being even more efficient than structs.
The point is, structs (a distinct _language_ construct) are often used
as performance-enhancement compromises, but optimizations are at the
discretion of the implementor, whether it's structs or optimized CLOS.
> > - Structs are "just" different (e.g., they generate make- functions)
> > -> This is historical baggage.
>
> See my paper on rapid prototyping in Lisp Pointers for why this is more
> than you say. The presence of "ready-made" stuff contributes to rapid
> prototyping and is not to be discounted.
I did not say automatic function generation is good or bad. The idea is
by and large orthogonal to what type of object it is applied to.
> Since
> there is nothing into which people can evacuate many present uses of
> defstruct, it is plain that structs cannot be deprecated.
If the particular features of structs can be merged with CLOS, why not?
Moreover, many things are already just macros, like DEFSTRUCT. Isn't
there enough commonality to make such a transition techically feasible?
> But I think you are simply technically wrong in your claim that structs
> could be deprecated
You are right, I should have used the term "unified with or merged into
CLOS". As for "technically", I am trying to see arguments that make
such a unification technically unreasonable. You have given excellent
and totally valid economical and political reasons. Your contrasting of
boundness with type is interesting, as I assumed that a type declaration
would exclude the possibility of unboundness at access time.
> I can make technical arguments all day, but I am free already not
> to use them.
It's fine, but it creates a self-amplifying loop of using structs for
their speed and fine-tuning the implementation of structs for even more
speed, where the same efforts could go towards fine-tuning the more
general brother, CLOS. Yes, users are free not to use structs, but no,
the existence of structs is not necessarily neutral to those users.
Duane Rettig had an interesting comment that he is not getting payed for
adding KLOCs, he is getting payed for removing them. I am just arguing
that there is enough commonality between structs and classes to make a
unification feasible and desirable on a technical basis, and the
peculiarities (accidental or intended) don't seem impossible to be taken
care of.
I'm sure my comments are a lot to do with the perspective of a language
user rather than implementor. In my work, eliminating functional
overlaps has usually been proven desirable, though it sometimes required
agreements with my users. The exception is when I let alternative
solutions co-exist temporarily, just to see their survival capabilities.
Robert
> Quoting from your excellent Hyperspec:
Hey, I don't have any ego bound up in this, so calling it "excellent"
won't buy you any debate points here. ;-)
> "The :type slot option specifies that the contents of the slot will
> always be of the specified data type. It effectively declares the result
> type of the reader generic function when applied to an object of this
> class. The consequences of attempting to store in a slot a value that
> does not satisfy the type of the slot are undefined."
>
> Does this not mean that the slot contents could be assumed to be of a
> certain type by unsafe code? Does not this mean that the implementor is
> allowed to either require an :initarg or have the slot take on an
> undefined value (as customary with float arrays) in unsafe code?
I don't personally think so, but I'm not sure. However, it wouldn't
explain the behavior of SLOT-MAKUNBOUND, which would effectively mean
"randomize this cell's value"; nothing the spec leads someone to believe
this--certainly the wording of SLOT-MAKUNBOUND's definition doesn't lead
me to think that's what's going on.
> In any case, it's nit-picking, because the standard does not say that
> structs must be implemented efficiently, and it does not prohibit CLOS
> from respecting declarations or being even more efficient than structs.
> The point is, structs (a distinct _language_ construct) are often used
> as performance-enhancement compromises, but optimizations are at the
> discretion of the implementor, whether it's structs or optimized CLOS.
This is a possible point of view, but I doubt it will hold up under
scrutiny. I think overall less is said to impede structs than to impede
CLOS. Lots of verbiage all over the place is said about standard-class to
give it more well-formedness than the other metaclasses, and I'm doubtful
you can really show there is more flexibility for standard classes than for
structure classes. However, you are right in saying such efficiency isn't
required--the marketplace is expected (by more-or-less explicit consensus
of the committee at multiple points along the way in our design)
to sort that part out ... and the marketplace accomplishes that.
> > > - Structs are "just" different (e.g., they generate make- functions)
> > > -> This is historical baggage.
> >
> > See my paper on rapid prototyping in Lisp Pointers for why this is more
> > than you say. The presence of "ready-made" stuff contributes to rapid
> > prototyping and is not to be discounted.
>
> I did not say automatic function generation is good or bad. The idea is
> by and large orthogonal to what type of object it is applied to.
My point was that if you hold the language spec fixed (and I think we
must, at least for this discussion, and maybe for the practical
future; see below), one of these operators does the auto-generation,
the other does not. The choice to deprecate is extralinguistic but
must be made on the basis of "usefulness". Since DEFSTRUCT provides a
functionality that DEFCLASS does not (as a matter of fact, not a
matter of possible other universes), deprecation of that functionality
would force people to eschew the only present source of that rapid
prototyping capability, and would not be doing well by the language.
> [... much removed ...]
> I am just arguing
> that there is enough commonality between structs and classes to make a
> unification feasible and desirable on a technical basis, and the
> peculiarities (accidental or intended) don't seem impossible to be taken
> care of.
Here again is where I disagree, but on a different basis. We talked a lot
this time around about whether to change the core language to fix this and
numerous other warts of the langauge. Merely even opening the core language
to any kind of change is dangerous. We cannot, in advance, make people
promise what kind of changes are ok. We can only open the political process
to take its course or not. And doing that guarantees that all presently-solid
code everywhere will have to be reviewed because it may be subject to small
changes. The cost of this for CLTL2=>ANSI was large, and CLTL2
self-identified as a non-standard that people weren't to program to.
The cost to companies adopting even tiny changes to a language can EASILY run
(and I've seen the bug reports and consultant analyses to back this up)
in the tens or hundreds of thousands of dollars. To what end? What we have
is good enough that we decided to fix it.
There's a strong lesson here, and it's related to the issue of people
perfecting Scheme to death that we talked about recently in another thread:
The market experiments and then the market moves on. The dollars of the
world are invested in progress, not in infinite refinement. Betamax and
VHS are the example I always use, since everyone I know who might understand
it well enough says betamax was technically better. But VHS won, and the
market has not been ill-served by continuing with VHS, because it's more
important to have something be standard and to be able to build on it
reliably than it is to have it be right-but-ever-shifting and to never
be able to build above it.
Fundamentally, Lisp in the marketplace is ailing for lack of integration of
DEFCLASS and DEFSTRUCT, and any money spent fixing this is ill-spent.
Lisp is ailing for lack of ability to move forward--lack of an RMI bridge
to Java, lack of packaged classes for doing standard kinds of things that
everyone needs to do like graphics, sounds, fonts, and even credit card
processing, eService contracts, and so on.
> I'm sure my comments are a lot to do with the perspective of a language
> user rather than implementor. In my work, eliminating functional
> overlaps has usually been proven desirable, though it sometimes required
> agreements with my users. The exception is when I let alternative
> solutions co-exist temporarily, just to see their survival capabilities.
Lisp has provided you personally with the ability to make your own package
and to redefine things to your heart's content until you have the things
you want. If the implementations aren't fast enough, take it up with your
vendor. If the language isn't right, that's what MAKE-PACKAGE and SHADOW
are for. Or you can just decline to use things that offend you, or you can
write style books to hope others will follow you.
Personally, I often advise people to just not care about efficiency at all
because, to a close approximation, it's almost always wrong to care. And in
the few cases where it comes to matter, it's easier to fix as a bug than
to have built it in from the start. If you follow this, you'd probably be
like me and mostly use DEFCLASS regardless. [Though sometimes I still
just use DEFSTRUCT 'cuz I like the textual simplicity of
(defstruct kons kar kdr).]
But I think we can learn to live with the spec as it is. That isn't me
saying aesthetics don't matter. Or that people should stop ragging on my
language becuase it offends me. It's me saying that at the end of the
day, aesthetics only matter so much and there's only so much time before we
die that we should allocate to ragging on things that are wrong. If anything
we plan to do in our lives is going to matter, it needs to be forward looking,
not backward looking, or we haven't fulfilled our moral obligation to society
to put back into it as much as it has contributed to us and to keep things
moving forward.
Aesthetics can be added as an afterthought; it doesn't have to be
built in from the core. If you don't believe me, look at any piece of
art and then examine very closely the structure of the underlying
fabric of paint or steel. You'll see the imperfections. But you'll
see the artist didn't obsess about removing them; he or she learned to
work with them, to play with them, to ignore them, or to rise above
them. The would-be artist that refuses to use a paintbrush because
it's imprecise isn't ever going to be an artist because that person has
their priorities and expectations about the world set wrong and/or has
no vision of the importance of learning to just blunder ahead and see
what lies beyond.
[elided comments appreciated]
> If you take the stance that defun should be deprecated, then you'd
> presumably want e.g. car to be a gf, too.
CAR was a smart, but controversial choice here. First, a generic
function should have a name that accomodates genericity. During design,
I spend a non-trivial amount of time to think about good function names,
because the choice of something like frob makes the meaning of the GF
somewhat of a moving target. (OK, :documentation is invented, but a
good name should be an essential part of any documentation.)
Now, CAR has lost its meaning as a function name, let alone as a generic
function name. Let's choose +. Let's assume CLOS was invented before
the variety of number types.
Yes, I would suggest that + is implemented as a generic function. Why?
- It has a meaningful name
- It can have a good definition (quoted from the Hyperspec):
"Returns the sum of numbers, performing any necessary type
conversions in the process. [...]"
- Extension is expected (e.g., to complex numbers)
Optimizations are possible, based on the declared type of arguments
(possibly open-code the appropriate method)
Back to reality: if we look at the current implementation of +, it oozes
both genericity and optimizability. It happens to predate CLOS, but
many good ideas are already there, and it is even more ahead in
optimization.
Back to CAR: a funcion with this name can not be generalized, but we
have FIRST.
> Every GF is (part of) an (internal) _interface_ that indicates that as
> long as you follow any additional constraints set out in the
> documentation of the interface, you are free to extend the GF with
> your own methods and expect it to work reliably.
I agree, but CAR could be read as the "head of the cons cell"
(alternatively take "contents of address register"), making it as
specific as invert-binary-digit or modify-overdue-home-loan-only.
Just how specific can a function be to the class of its argument? CDR
_has_ been gereralized to multiple types of cons cells (used in
CDR-coding). Even CAR may have (and conceptually does have) various
methods: it has a method specialized on a cons cell, and a conceptual
:around method whose role is to validate the argument.
> E.g. I think it is perfectly acceptable for some function foo to be
> called each time something happens in one version, and then only the
> first time something happens in a new version, without anyone getting
> notified about it. For a GF, I'd hesitate making this change, and I'd
> make sure that anyone depending on the exact semantics of the GF will
> get notified.
Isn't it mostly a question of what you make available for your users?
Is it your concern that maybe someone would tuck an :after method on
your GF, whose name is exported? An example (not necessarily code)
would help.
> Just give another example: As part of our simulation toolkit, I've
> implemented a generic priority queue implementation. Of course I've
> used CLOS to give an extensible interface for priority-queues:
[...]
> OTOH the different implementations (naive linear lists, splay trees,
> skew heaps, binomial heaps...) don't use GFs or classes internally
Thanks for the example. What do you do if you have a couple of
competing implementations for skew heaps? Maybe only some details
differ relative to one another. Using GFs would greatly help you in
maintaining competing versions (i.e., no copy-paste of code just so that
you can rename it nmerge-skew-heap-1, nmerge-skew-heap-2 etc. and
maintain them in parallel).
OTOH, accepting your view (for sake of argument :-) maybe
nmerge-skew-heap can be a FLET for even better locality?
Thanks for your answer,
Robert (who is using both CAR and FIRST)
I mentioned these representations, but the point is taken as there may
be other babies I don't know about. Can someone give examples for
effectively using some unique aspect of structs? (For example, Kent
mentioned rapid prototyping).
> I implore you all to consider what it would take to change your mind
> on whatever it is you believe. Methodologically, it has no value to
> have yet another affirmation of some claim if no counter-claims have
> been allowed. Some defenders of the faith (because that is what it
> becomes) believe that counter-information, counter-views, etc, are
> "subversive" and "dangerous" and should be controlled or stopped
Dangerous thoughts - stop already!
Robert
> The cost to companies adopting even tiny changes to a language can EASILY run
> (and I've seen the bug reports and consultant analyses to back this up)
> in the tens or hundreds of thousands of dollars. To what end? What we have
> is good enough that we decided to fix it.
There is also the cost to implementors. Ada 95 added a lot of things
above and beyond Ada 83 and is by most criteria a far better language,
but the cost to implement these features made a number of Ada 83
compiler vendors decide to discontinue their offerings in this market.
> Fundamentally, Lisp in the marketplace is ailing for lack of integration of
^ not?
> DEFCLASS and DEFSTRUCT, and any money spent fixing this is ill-spent.
--
Lieven Marchand <m...@bewoner.dma.be>
Lambda calculus - Call us a mad club
>
> Kent M Pitman <pit...@world.std.com> writes:
...
> > Lisp in the marketplace is ailing for lack of integration of
> ^ not?
No, I did not mean to insert a "not" here, but others might choose
differ with me on this point.
I think Lisp is getting completely beaten up for lack of library support
compared to Java, which I claim proves my point that it's more important
to be standard than to be right. Java sucks as a language, but it is stable
enough that people have invested in tons of libraries. Lisp should seek to
annex them by making them reliably available/callable in all implementations.
> Lieven Marchand <m...@bewoner.dma.be> writes:
>
> >
> > Kent M Pitman <pit...@world.std.com> writes:
> ...
> > > Lisp in the marketplace is ailing for lack of integration of
> > ^ not?
>
> No, I did not mean to insert a "not" here, but others might choose
> differ with me on this point.
I think Lieven meant the paragraph before the one you think, i.e. the
one where you say:
> Fundamentally, Lisp in the marketplace is ailing for lack of integration of
> DEFCLASS and DEFSTRUCT, and any money spent fixing this is ill-spent.
and not the one after that where you say:
> Lisp is ailing for lack of ability to move forward--lack of an RMI bridge
Regs, Pierre.
> Lieven Marchand <m...@bewoner.dma.be> writes:
>
> >
> > Kent M Pitman <pit...@world.std.com> writes:
> ...
> > > Lisp in the marketplace is ailing for lack of integration of
> > ^ not?
>
> No, I did not mean to insert a "not" here, but others might choose
> differ with me on this point.
>
> I think Lisp is getting completely beaten up for lack of library support
> compared to Java, which I claim proves my point that it's more important
> to be standard than to be right. Java sucks as a language, but it is stable
> enough that people have invested in tons of libraries. Lisp should seek to
> annex them by making them reliably available/callable in all implementations.
I *totally* and *wholeheartedly* agree with (parts of) this statement.
However.
There two courses of action.
1 - Make a nice JNI interface for Java classes.
2 - Build an equivalent and coherent set of libraries for CL.
Assuming that there are the resources to do so (always the crux of the
problem), I believe that we need both.
ACL 6.0 will provide Java integration. This is good. The other
implementations should follow suit, and implement the ACL interface
themselves (at least a core fo common functionality).
As for other libraries, I believe that until a little while ago -
allow me a shameless plug here - we had lacked some key pieces of
technology to make "more portable" CL libraires. That is why I cared
so much for MK:DEFSYSTEM, and why I wrote CL-ENVIRONMENT and
CL-CONFIGURATION.
Finally, I do not know what the status of the X3J13 committee is.
There is definitively a role for it in the near future.
> Lieven Marchand <m...@bewoner.dma.be> writes:
>
> >
> > Kent M Pitman <pit...@world.std.com> writes:
> ...
> > > Lisp in the marketplace is ailing for lack of integration of
> > ^ not?
>
> No, I did not mean to insert a "not" here, but others might choose
> differ with me on this point.
I think we're in violent agreement. The line quoted was over the
integration of DEFCLASS/DEFSTRUCT.
> Kent M Pitman <pit...@world.std.com> writes:
>
> > Lieven Marchand <m...@bewoner.dma.be> writes:
> >
> > >
> > > Kent M Pitman <pit...@world.std.com> writes:
> > ...
> > > > Lisp in the marketplace is ailing for lack of integration of
> > > ^ not?
> >
> > No, I did not mean to insert a "not" here, but others might choose
> > differ with me on this point.
>
> I think we're in violent agreement. The line quoted was over the
> integration of DEFCLASS/DEFSTRUCT.
Right. Oops. That's what you get for overabbreviating your outtakes.
Thanks to you and others for clarifying, though.