Janis Dzerins <jo...@latnet.lv> writes: > 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 ...))
> 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 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.
> > > 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.
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
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?
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
> > 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?
> 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
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.
Janis Dzerins wrote: > 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.
Kent M Pitman wrote: > > 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).
"Pierre R. Mai" wrote: > 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:
> 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).
<monf...@fisec.com> wrote: > - 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.
Robert Monfera <monf...@fisec.com> writes: > > 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:
> 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.
-- Pierre R. Mai <p...@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
* Robert Monfera <monf...@fisec.com> | 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).
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.
#:Erik -- Does anyone remember where I parked Air Force One? -- George W. Bush
Robert Monfera <monf...@fisec.com> writes: > - 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.
* Robert Monfera wrote: > - 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...
> > - 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.
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 Monfera <monf...@fisec.com> writes: > 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.
> 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)
> * Robert Monfera <monf...@fisec.com> > | 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).
> 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 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
> 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.
> > 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.
-- Pierre R. Mai <p...@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
> > 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.
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