How do I prevent a class from being instantiated under any circumstances? I am constructing a largish class tree and the rootmost classes are really only meant as umbrella classes that shouldn't be instantiated at any time. I'm not sure exactly how to prevent them from being instantiated, however. Any ideas?
'james
-- James A. Crippen <ja...@unlambda.com> ,-./-. Anchorage, Alaska, Lambda Unlimited: Recursion 'R' Us | |/ | USA, 61.20939N, -149.767W Y = \f.(\x.f(xx)) (\x.f(xx)) | |\ | Earth, Sol System, Y(F) = F(Y(F)) \_,-_/ Milky Way.
In article <m3vgfu5mgo....@kappa.unlambda.com>, ja...@unlambda.com
(James A. Crippen) wrote: > How do I prevent a class from being instantiated under any > circumstances? I am constructing a largish class tree and the > rootmost classes are really only meant as umbrella classes that > shouldn't be instantiated at any time. I'm not sure exactly how to > prevent them from being instantiated, however. Any ideas?
Presumably you can add a method to make-instance, specialized on the class object a a singleton, and have that method throw an error.
> How do I prevent a class from being instantiated under any > circumstances? I am constructing a largish class tree and the > rootmost classes are really only meant as umbrella classes that > shouldn't be instantiated at any time. I'm not sure exactly how to > prevent them from being instantiated, however. Any ideas?
I assume you are asking the CLOS counterpart to C++ abstract classes. How would it be possible in your application to arbitrarily instantiate any class?
Solution #1 This is not meant to be flippant. Just never write any code that instantiates the class.
In article <m3vgfu5mgo....@kappa.unlambda.com>, James A. Crippen wrote: >How do I prevent a class from being instantiated under any >circumstances?
One good way is not to write that class, and consequently not encumber the user of the class with your restrictions.
> I am constructing a largish class tree and the >rootmost classes are really only meant as umbrella classes that >shouldn't be instantiated at any time. I'm not sure exactly how to >prevent them from being instantiated, however. Any ideas?
How about writing some documentation which describe the purpose of your classes according to your view of the world as their author. People that agree will then not instantiate them.
> How do I prevent a class from being instantiated under any > circumstances? I am constructing a largish class tree and the > rootmost classes are really only meant as umbrella classes that > shouldn't be instantiated at any time. I'm not sure exactly how to > prevent them from being instantiated, however. Any ideas?
> 'james
> -- > James A. Crippen <ja...@unlambda.com> ,-./-. Anchorage, Alaska, > Lambda Unlimited: Recursion 'R' Us | |/ | USA, 61.20939N, -149.767W > Y = \f.(\x.f(xx)) (\x.f(xx)) | |\ | Earth, Sol System, > Y(F) = F(Y(F)) \_,-_/ Milky Way.
"Wade Humeniuk" <humen...@cadvision.com> writes: > > How do I prevent a class from being instantiated under any > > circumstances? I am constructing a largish class tree and the > > rootmost classes are really only meant as umbrella classes that > > shouldn't be instantiated at any time. I'm not sure exactly how to > > prevent them from being instantiated, however. Any ideas? > Solution #2
[ Sidenote: In CMU CL you need to use pcl::find-class here ]
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
> > > How do I prevent a class from being instantiated under any > > > circumstances? I am constructing a largish class tree and the > > > rootmost classes are really only meant as umbrella classes that > > > shouldn't be instantiated at any time. I'm not sure exactly how to > > > prevent them from being instantiated, however. Any ideas?
I'll try this. The system is designed to be dynamically extensible by somewhat random users so I can't just rely on not writing code that instantiates. Some random user might get it in their head to try instantiating one of these abstract classes and then various assumptions in the rest of the system (that I'd rather not have to explicitly check for) would be invalid.
Of course if I have to extend make-instance the question arises whether this might be costly. I expect to see lots of instantiation with no regular pattern during use, so I suppose that I have to decide which would cost more in the long term: writing explicit checks all over where assumptions are being made, or slowing down the make-instance generic with an extra eql method.
'james
-- James A. Crippen <ja...@unlambda.com> ,-./-. Anchorage, Alaska, Lambda Unlimited: Recursion 'R' Us | |/ | USA, 61.20939N, -149.767W Y = \f.(\x.f(xx)) (\x.f(xx)) | |\ | Earth, Sol System, Y(F) = F(Y(F)) \_,-_/ Milky Way.
In article <m3r8qi55o0....@kappa.unlambda.com>, ja...@unlambda.com
(James A. Crippen) wrote: > Of course if I have to extend make-instance the question arises > whether this might be costly. I expect to see lots of instantiation > with no regular pattern during use, so I suppose that I have to decide > which would cost more in the long term: writing explicit checks all > over where assumptions are being made, or slowing down the > make-instance generic with an extra eql method.
The reasonable implementation of eql method dispatch is a hash table, the speed of which should not depend on the number of methods.
>I'll try this. The system is designed to be dynamically extensible by >somewhat random users so I can't just rely on not writing code that >instantiates. Some random user might get it in their head to try >instantiating one of these abstract classes and then various >assumptions in the rest of the system (that I'd rather not have to >explicitly check for) would be invalid.
Another possibility using a mixin and MOP (a class is an abstract-class if the mixin is one of its direct superclasses):
It is easy to use. Makes also easy to collect or browse all the abstract classes
You could define a macro such as : (def-abstract abstract-class-1 () ())
This will allow to change easily the implementation if performance is a problem.
>Of course if I have to extend make-instance the question arises >whether this might be costly. I expect to see lots of instantiation >with no regular pattern during use, so I suppose that I have to decide >which would cost more in the long term: writing explicit checks all >over where assumptions are being made, or slowing down the >make-instance generic with an extra eql method.
> How do I prevent a class from being instantiated under any > circumstances? I am constructing a largish class tree and the > rootmost classes are really only meant as umbrella classes that > shouldn't be instantiated at any time. I'm not sure exactly how to > prevent them from being instantiated, however. Any ideas?
Have a look at my abstract-class (no instances) / final-class (no subclasses) stuff. It uses the MOP but is known to work in at least ACL, LW, and CMUCL (and genera, or so the source claims!).
It's at http://www.tfeb.org/lisp/hax.html#ABSTRACT-CLASSES (this URL will likely change as I have a huge chunk of stuff I want to put up there and reorganise it all, but probably there will be a forwarding pointer at least, and anyway it will be about 10 years before I get around to doing it...)
Funny thing is, despite all this work, you would still have to worry about someone calling change-class on any old instance... Wonder if sticking the error message on to shared-initialize might help?
I'm with those that think disallowing instantiation might not be such a great thing to try. Getting into the MOP is relying on a vendor-specific standard, and thus has its own problems. The only other reliable way I can think of is to list every function that might be used to create or modify the instance, and stick an error message on to it.
[ The rest of this message is directed at James Crippen, the original poster...]
And before doing so, I'd ask myself if it is worth the trouble... The reason for the meta question is that it is highly improbable that lisp programmers would try to instantiate the root class instance, if you advise against it in your documentation. (What would they do with it anyway?) What you might be looking for is the detection of accidents. I don't think that should be your responsibility, at least while you are dealing with decent lisp programmers. Now, if you are using this as a pedagogical tool, or aiming it at other lisp newbies, it might be a worthwhile goal. But that doesn't sound like what you are doing either.
Perhaps we ought to help with your intent rather than the question you ask?
> then inherit instantiable as needed. or flip the arrow with an ABSTRACT > mixin class.
> if you want fully to recreate C++ under CL, maybe a metaclass would be > in order.
> kenny > clinisys
> "James A. Crippen" wrote:
>>How do I prevent a class from being instantiated under any >>circumstances? I am constructing a largish class tree and the >>rootmost classes are really only meant as umbrella classes that >>shouldn't be instantiated at any time. I'm not sure exactly how to >>prevent them from being instantiated, however. Any ideas?
>>'james
>>-- >>James A. Crippen <ja...@unlambda.com> ,-./-. Anchorage, Alaska, >>Lambda Unlimited: Recursion 'R' Us | |/ | USA, 61.20939N, -149.767W >>Y = \f.(\x.f(xx)) (\x.f(xx)) | |\ | Earth, Sol System, >>Y(F) = F(Y(F)) \_,-_/ Milky Way.
Sunil Mishra <smis...@notmyemail.com> writes: > Funny thing is, despite all this work, you would still have to worry > about someone calling change-class on any old instance... Wonder if > sticking the error message on to shared-initialize might help?
CHANGE-CLASS is a generic function as well, you can always "block" that using the same EQL trick you use on MAKE-INSTANCE.
All in all I think MAKE-INSTANCE and CHANGE-CLASS are the right places where to intervene, since the are not operating on an instance yet.
Of course it's two places to change....
Cheers
-- Marco Antoniotti ======================================================== NYU Courant Bioinformatics Group tel. +1 - 212 - 998 3488 719 Broadway 12th Floor fax +1 - 212 - 995 4122 New York, NY 10003, USA http://bioinformatics.cat.nyu.edu "Hello New York! We'll do what we can!" Bill Murray in `Ghostbusters'.
> > The reasonable implementation of eql method dispatch is a hash table, > > the speed of which should not depend on the number of methods.
> Yes, but the reasonable implementation of MAKE-INSTANCE with a known > argument is often inlined code...
But if the argument is known, having a non-matching method will not inhibit that expansion from happening (the expansion will still have to check for new methods at run-time at any rate).
And the speed of cases where the eql method matches is not very relevant, given that an error will be raised... ;)
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
> I'll try this. The system is designed to be dynamically extensible by > somewhat random users so I can't just rely on not writing code that > instantiates. Some random user might get it in their head to try > instantiating one of these abstract classes and then various > assumptions in the rest of the system (that I'd rather not have to > explicitly check for) would be invalid.
When feasible, it would be better to ensure that the "abstract" classes do meet the assumptions, maybe by providing "harmless" defaults.
If not then doing the make-instance hack, or something similar is probably your best bet.
> Of course if I have to extend make-instance the question arises > whether this might be costly. I expect to see lots of instantiation > with no regular pattern during use, so I suppose that I have to decide > which would cost more in the long term: writing explicit checks all > over where assumptions are being made, or slowing down the > make-instance generic with an extra eql method.
I don't think that defining such an eql method will have a noticable effect on make-instance calls. On CMUCL, there is no effect in calls with either constant or dynamic class arguments.
It usually pays to not worry about CLOS performance, except for the very tightest inner loops.
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
> > The reasonable implementation of eql method dispatch is a hash table, > > the speed of which should not depend on the number of methods.
> Yes, but the reasonable implementation of MAKE-INSTANCE with a known > argument is often inlined code...
Right. The table lookup is then done at compile-time. Maybe that's not so easy to do in CL, but in a language with sealing declarations it's possible (and common) to add a method to make() and still get inline code. It's quite common for Dylan programs to have abstract classes which overide make() to return an instance of a concrete subclass e.g. make(<vector>) returns a <simple-object-vector>.
I guess I should try it with CMUCL, and see what happens...
Wade Humeniuk wrote: >>How do I prevent a class from being instantiated under any >>circumstances? I am constructing a largish class tree and the >>rootmost classes are really only meant as umbrella classes that >>shouldn't be instantiated at any time. I'm not sure exactly how to >>prevent them from being instantiated, however. Any ideas? > Solution #2
> > > The reasonable implementation of eql method dispatch is a hash table, > > > the speed of which should not depend on the number of methods.
> > Yes, but the reasonable implementation of MAKE-INSTANCE with a known > > argument is often inlined code...
> Right. The table lookup is then done at compile-time. Maybe that's not > so easy to do in CL, but in a language with sealing declarations it's > possible (and common) to add a method to make() and still get inline > code. It's quite common for Dylan programs to have abstract classes
I fail to see how sealing is relevant in this context. make can't be a sealed GF, so it is possible that methods are added and removed at run-time. Hence the compiler can IMHO only tentatively inline the code, bailing out to a more general mechanism at run-time if any relevant methods have been added/removed.
The situation is identical in CL.
[There are differences, in that the inlined code will also have to check for changes in the class-hierarchy (including the effects of class redefinition), which Dylan can sometimes avoid, given sealing declarations for the relevant classes. That shouldn't come into play here at all]
> I guess I should try it with CMUCL, and see what happens...
See my other message in this thread.
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
> > > > The reasonable implementation of eql method dispatch is a hash > > > > table, > > > > the speed of which should not depend on the number of methods.
> > > Yes, but the reasonable implementation of MAKE-INSTANCE with a known > > > argument is often inlined code...
> > Right. The table lookup is then done at compile-time. Maybe that's > > not > > so easy to do in CL, but in a language with sealing declarations it's > > possible (and common) to add a method to make() and still get inline > > code. It's quite common for Dylan programs to have abstract classes
> I fail to see how sealing is relevant in this context. make can't be > a sealed GF, so it is possible that methods are added and removed at > run-time. Hence the compiler can IMHO only tentatively inline the > code, bailing out to a more general mechanism at run-time if any > relevant methods have been added/removed.
> The situation is identical in CL.
No, because sealing the whole GF isn't the only option -- in Dylan you can seal only *part* of the GF.
With these declarations, "make" and "initialize" are inlined and don't check anything, even though their GFs are open. Anything in another library or at runtime that would invalidate the assumptions made by this optimization will throw an error, so safety is maintained.
"functional" is an extension to the DRM language spec. It enables objects of the class to be stored on the stack and passed by copying rather than allocating them on the heap and passing them by reference. e.g. it causes "eq" to be defined as the same as "equal". Sometimes this is a win, sometimes it isn't. In this case it proves to be a big big win.
> [There are differences, in that the inlined code will also have to > check for changes in the class-hierarchy (including the effects of > class redefinition), which Dylan can sometimes avoid, given sealing > declarations for the relevant classes. That shouldn't come into play > here at all]
"define sealed domain" was a relatively recent addition to Dylan, but has been there about as long as the infix syntax, I think. In the Apple Technology Release compiler and documentation (the prelinimary DRM) it was called "define inert domain", but it appears to be exactly the same feature.
Adding the "sealed" attribute to a slot applies "define sealed domain" to its getter and setter GFs. Adding the "sealed" attribute to a method applies "define sealed domain" to that GF with those argument types.
> > I guess I should try it with CMUCL, and see what happens...
From what everyone on this thread has said, I've come to the conclusion that the best thing I can do is make it very clear to the users that instantiation of the abstract classes is a bad thing, noting it in documentation and source code. Also, adding some checks for assumptions about the abstractness of classes in the places where it will do the most good for preventing mistakes. Being Lisp I'm not going to write *everything* in the style of an overdefensive C programmer, but I won't gratuitously assume that everything in the system is in perfect working order.
I'm with those people who feel that mucking with the MOP is a bad idea because of the inherent portability problems that can occur. I'd really rather not end up having to debug MOP problems that could have been avoided otherwise.
'james
-- James A. Crippen <ja...@unlambda.com> ,-./-. Anchorage, Alaska, Lambda Unlimited: Recursion 'R' Us | |/ | USA, 61.20939N, -149.767W Y = \f.(\x.f(xx)) (\x.f(xx)) | |\ | Earth, Sol System, Y(F) = F(Y(F)) \_,-_/ Milky Way.
In article <m38zcp54qr....@kappa.unlambda.com>, ja...@unlambda.com
(James A. Crippen) wrote: > From what everyone on this thread has said, I've come to the > conclusion that the best thing I can do is make it very clear to the > users that instantiation of the abstract classes is a bad thing, > noting it in documentation and source code.
Something no one has mentioned, and that I meant to but forgot...
Even if you add a method to, say, make-instance, there is nothing to stop a determined user from removing it again, or adding their own method that just calls next-method in place of yours.
Stroustrup says that the protection mechanisms in C++ are to prevent accident, not fraud. I think that applies here too. If it was important I think I'd probably add a method to make-instance rather than rely *only* on the documentation. That's not totally foolproof as there is always a bigger fool (cue: Erik), but it's probably enough.
In article <m38zcp54qr....@kappa.unlambda.com>, James A. Crippen wrote: >From what everyone on this thread has said, I've come to the >conclusion that the best thing I can do is make it very clear to the >users that instantiation of the abstract classes is a bad thing, >noting it in documentation and source code.
In Lisp, you don't need abstract classes, because an object doesn't have to declare that it inherits a base class in order to be eligible to implement the interface.
An interface in Lisp is, roughly, a collection of generic functions. Methods provide the implementation; you define methods that take arguments of your classes and that's it. There is no commonality that needs to be factored out into bases that everyone must declare in their supeclass list.
Interface bases are simply an artifact of a static type system. The whole purpose of an abstract base class in C++ is to make its single dispatch run fast; the program has something to aim a pointer at so it can get a hold of a vtable, and indirectly call to the implementation of a virtual function in some base somewhere. The type of the base class pointer, along with other constraints in the language, provide static assurance that the machine language translation can blindly index into the vtable and perform an indirect call.
In other words, declaring inheritance from an empty base class, or declaring that a class implemenets some interface, is a form of optimization, like declaring a variable to be a certain kind of integer. It has nothing to do with object-orientation.
* James A. Crippen | From what everyone on this thread has said, I've come to the conclusion | that the best thing I can do is make it very clear to the users that | instantiation of the abstract classes is a bad thing, noting it in | documentation and source code. Also, adding some checks for assumptions | about the abstractness of classes in the places where it will do the most | good for preventing mistakes. Being Lisp I'm not going to write | *everything* in the style of an overdefensive C programmer, but I won't | gratuitously assume that everything in the system is in perfect working | order.
The near hysterical need to protect programmers from doing stupid things has always mystified me. E.g., if you need to control access to your class slots with public, protected, and private, this only means that you are telling too many people too much. If you give people a source file that contains all the names of all the slots with a silly string to make access difficult, all that your protection scheme relies on is that your users are unable to edit that file, which is obviously nuts. Instead of protecting yourself from problems, you force people who think you made a mistake to edit the file or make a copy, and then they get out of synch. Of course, people would go "oh, no, you should not do that" if you said you had done this, but that is the core of this social protection scheme -- it has _nothing_ to do with what is in those files. Since programmers who want to add a method to a supposedly public class will have to engage in header file editing, already, the threshold to do this is much lower than one might think. Of course, maintaining "bit-level compatibility" with other code compiled with the unedited header file is kind of hard, and may lead to all sorts of hard-to-detect problems, but trust me, this is done all the time, because the languages are so stupidly designed.
Common Lisp tends to assume that programmers are not lunatics who need to be protected from themselves. So instead of forcing people to read and then edit or copy "header files", they have the introspective abilities they need right there in the development system. And Common Lisp folks go "oh, no, you should not do that" if you use slot-value to access a slot that has not be documented to be publicly accessible.
Similarly, the "pure abstract class" is a compile-time issue in some "object-oriented" languages, but if you trust programmers not to do so many stupid things, the only protection you need is already there: There are presumably no generic functions that would dispatch on the class that should never be instantiated. (This would apply to mixin-classes, for instance, which are only supplying some new slots and/or supply a method that is already known to exist in the classes they are mixed in with.)
In other words, you should already have the necessary mechanism in your code if you simply omit methods that dispatch on the abstract class. Is this not sufficient?
/// -- The past is not more important than the future, despite what your culture has taught you. Your future observations, conclusions, and beliefs are more important to you than those in your past ever will be. The world is changing so fast the balance between the past and the future has shifted.
* Bruce Hoult | That's not totally foolproof as there is always a bigger fool (cue: | Erik), but it's probably enough.
Are there really no psychiatrists near you who could help you get over your coping problems? No friends or anybody else who cares about you who could tell you to get over your personal problems? No girlfriend, not even a _pet_ who can tell you that you have lost your mental balance?
You keep attacking me for no reason at all. I find your behavior pattern evidence that you actually _are_ retarded, specifically: devoid of the prerequisite intelligence to understand what you are doing to yourself. Thus, you are not _able_ to control yourself, but will continue to make similar idiotic remarks in the future, in order to prove that you are an asshole on a mission. Your next idiotic remark of the same kind will be the only proof we need of your mental state and that you should be put in global kill-files.
/// -- The past is not more important than the future, despite what your culture has taught you. Your future observations, conclusions, and beliefs are more important to you than those in your past ever will be. The world is changing so fast the balance between the past and the future has shifted.