Sheeple is a Dynamic, CLOS-like Prototype-based Object-Oriented Programming Framework (or "POOP Framework") that strives to maximize application flexibility, minimize cost while maximizing value, optimize programmer time, and empower application implementers to better assist them leveraging modern paradigms in order to proactively achieve next-gen synergy in tomorrow's web 3.0 world. It is implemented in (mostly) ANSI Common Lisp. Sheeple is fully buzzword compliant.
Initially written as the object system of a text-based game online game engine (Sykosomatic), Sheeple is provided as a module separate from its persistent counterpart, Persistent-Sheeple, as a standalone library for use with any regular Lisp application.
Following the implementation of Sheeple's Metasheep Protocol, Persistent-Sheeple is being rewritten to take full advantage of the protocol, allowing parallel operation of regular sheeple with psheep.
Sheeple is inspired by a number of Object-Oriented systems, mainly:
- Common Lisp Object System (CLOS)
- LPC, a common language for building MUDs
- MOO, implemented and used by LambdaMOO
- Slate, a Smalltalk-like language with Prototype Multiple Dispatch that Sheeple bases its message dispatch system on.
It was written with the purpose of providing a lot of the goodies of CLOS programming in a completely prototype-based environment. As such, it shares a lot of features (and syntax) with CLOS. The most notable features are multiple inheritance and multimethods (called multimessages).
This README contains a basic usage guide, and sort-of-spec of Sheeple. Much heavier documentation, including a tutorial and a MSP spec is to come. Meanwhile, have fun reading the code (hohoho). (You can also contact me directly, using zkat located-at sykosomatic no-spam-dot org)
^this implementation makes use of trivial-garbage for weak-pointers, finalizers, and weak-hash-tables. The current version also only supports SBCL.
While portability is one of the goals for Sheeple, only SBCL is officially supported right now. It has been tested on several platforms and operating systems, and passes all tests on them.
It's fairly effortless to get Sheeple working. To get started, simply
(asdf:oos 'asdf:load-op 'sheeple) (in-package sheeple-user)
And mess around from there. Be aware that if your implementation does not include ASDF, you will have to acquire it and load it yourself. Clisp, for example, will require this. For information on how to do this, check out Cliki, or the ASDF Homepage.
Sheeple should work on most Lisp implementations, although it's mainly written and tested in SBCL on Linux x86.
FiveAM is required if you wish to run the test suite. To run it:
(asdf:oos 'asdf:load-op 'sheeple-tests) (sheeple-tests:sheeple-tests)
And watch the tests roll by. Only one should fail (CLOS Fleecing)
If you want to get Sheeple to run on your favorite platform, feel free to e-mail me: zkat . sykosomatic dot org, I'll help as much as I can (or even do the work for you). Any and all comments are also greatly appreciated.
Because of a combination of the necessity to avoid name collisions in a system very similar to CLOS and a desire to amuse myself (and others), Sheeple has been designed from the beginning to be maximally groan-worthy with its naming scheme. I apologize in advance, and I am not responsible for any issues the naming scheme might cause, such as Chronic Smug Weenie Nose-Tilting Syndrome.
I assure you, Sheeple is Serious Business(tm) for Serious Applications(tm).
Simple, but powerful defclass-like CLONE macro, with cloning options.
Dynamic object management tools (inspection of objects, addition/removal of properties, all without any sort of redefinition).
Dynamic property value access, following a prototype chain (the value of the nearest parent that set the value for a particular property is used if the child did not set one).
Full integration with built-in Lisp types (wolves) by transparent autoboxing (called fleecing).
Multiple inheritance through cloning with dynamic inspection and management (adding/removing) of parents.
Multiple dispatch on messages (methods) -- messages specialize on specific instances, and follow inheritance hierarchy. Multimessage definition is almost identical to CLOS methods, and shares similar semantics.
Dynamic removal of messages, as well as entire buzzwords with undefbuzzword/undefmessage.
Auto-generated readers/writers, with :manipulator, :reader, and :writer property options.
:before, :after, and :around messages
(call-next-message) and (next-message-p)
A sheep creation and property-access Metaobject Protocol, with a larger MOP in development.
Planned features that have not yet been implemented include:
CLOS integration, including autoboxing of entire class hierarchies.
Support more than just SBCL
Please refer to doc/user-guide for a quick-and-dirty intro to Sheeple and how to use it.
If you're extra-patient, you can wait for me to write a full-fledged user guide (slated for completion by the time 1.0 gets pooped out).
Again, you can always contact me directly with questions. I also regularly lurk in the #lisp channel on Freenode (as zkat).
I like CLOS. One of the things I want to make sure I can do with Sheeple is interoperate with CLOS code when I want (which isn't very hard in Lisp). There are certain problems I ran into, though, for which CLOS isn't as well suited as I'd want it to be. First, though, a short story...
When I started volunteering in a MUD I like, as a builder/programmer, I was able to play with a custom subset of LPC (a C-like prototype-based language) that the MUD used for game objects and scripting. Over time, I made a list of interesting tasks that were easy and useful in LPC, yet couldn't be done either as nicely, or at all, when using CLOS.
In a nutshell, the biggest advantages of using a prototype-based system are as follows:
Much greater dynamicity
Hierarchical data sharing
Expanding upon each...
In a prototype-based system such as sheeple, just about everything is fully dynamic, alterable at runtime. CLOS is, too, but certainly not to the same extent, or with the same ease. Sheeple allows you to change and fine-tweak individual objects at run-time, including changing their individual property definitions, their place on the prototype hierarchy, and the messages defined on them (including removing messages). This level of dynamicity is not necessary for most applications, but in certain scenarios (such as high-activity live applications that you might not want to stop and restart just to get some changes through), it can become very valuable. Using the example of the MUD, please imagine a simple fantasy game hierarchy with a =game-object= prototype, a =std-human= prototype, and finally some =charlie= object. The =game-object= object defines basic game object stuff, and =std-human= defines a (potentially large) set of characteristics that all humans should share. These are inherited by =charlie=, who is controlled by some user. In a regular class-based system, in order to change =std-human= and have the changes apply to =charlie=, one would have to write a fair amount of code very carefully to ensure that =charlie= was recreated and all the relevant values are copied over properly. Furthermore, anything that relied on =charlie='s object identity will potentially break unless the language is capable enough and yet more code was rewritten. If you just wanted to add a facial-hair slot, you might figure it's not worth it once you have several thousand humans running about, since that's several thousand potentially angry users. The situation in CLOS is less dire. The MOP allows easier changes to the 'schema' of these objects, but it still proves to be more of a hassle than I think it could be. In a system such as Sheeple, giving all humans (and thus, =charlie=) facial hair is a matter of simply setting the property in =std-human=. The new property will automatically apply to all descendants of =std-human=. Furthermore, changing =std-human= doesn't actually alter the structure of any of its descendants, so taking one of =std-human='s descendants and removing =std-human= as an ancestor would take away anything defined solely in =std-human=, but nothing directly defined in that descendant.
There's another interesting side effect of giving =std-human= facial hair: The value placed in that property will be the value that all of =std-human='s children use. So, if our new facial-hair property is set to "fuzzy beard" in =std-human=, =charlie= will automatically have a "fuzzy beard" (and so will all other descendants of =std-human=. This data sharing is critical in building certain kids of systems where being able to share a value are critical (such as cascading default values down the entire inheritance hierarchy). Again, this is also possible in class-based systems, but not without a web of code that simulates the behavior. An implementation-dependent advantage of a system like this is also that since all this data is being shared, it does not necessarily need to be copied over constantly. This can be leveraged to reduce both memory and CPU costs as necessary.
Related strongly to #1, but deserving a mention of its own is the effect of having the objects be their own classes. Doing so creates essentially a "bottomless" hierarchy, that can be expanded at will whenever the need arises. I feel this is of greater value when experimenting or rapid-prototyping, and I admit I am yet to find a solid advantage to this fact. The implication is certainly interesting, though. By "bottomless", I mean that you can have, say, =charlie=, who currently resides on the bottom of our pretend-hierarchy. For some reason, you may want to not only have an object that is a =std-human=, but in fact have one that is very much like =charlie=, and shares any traits (and methods) with him. Being able to construct your army of =charlie=-clones may not sound terribly exciting at the time, but my gut instinct tells me this is the path to world domination, so you better jump on the bandwagon.
It's worth noting that Sheeple does not require all values to be fetched through the hierarchy list. In certain cases, such as local lists, it might be troublesome to share data. In those cases, Sheeple provides something called "cloneforms", that work similarly to CLOS' :initform slot option.
If I haven't scared you off yet, maybe the following section will...
Why not Sheeple?
There are, of course, some caveats to using the current implementation of Sheeple...
It's considerably slower than CLOS -
This should not come as a surprise considering the sheer speed of implementations like PCL. Considerable optimization is planned as time goes on and interfaces stabilize, but Sheeple would most probably never be as fast as CLOS. On the other hand, it's considerable faster on SBCL than CLOS on GNU clisp, for both slot-access and dispatch, so it is certainly usable for a certain number of applications, even in this sub-optimal state.
It's buggy right now -
It really is. The API isn't guaranteed to stay as it is until 1.0 ships, and is regularly changing as requirements come up.
It doesn't have a toolkit as complete as CLOS' (yet) -
Conceptually, I think of Sheeple as a superset of CLOS: It can be made to easily emulate the behaviors of class-based systems through a number of tools (some of which are still in the works). A large number of CLOS goodies are planned, including a sizeable MOP, but it may never really reach CLOS-level glory (or bloat, depending on who you ask).
It's not CLOS
It looks a hell of a lot like CLOS, it might even trick people into thinking some jerk with a bad sense of humor wrote a bunch of alias macros around CLOS... but it's not CLOS. Many concepts are different, and it might be hard to get used to them, or even find any use in them. YMMV