Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

476 lines (313 sloc) 20.165 kb

About

Sheeple is a Dynamic, CLOS-like Prototype-based Object-Oriented Programming System (or "POOPS") 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:

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).

^this implementation makes use of trivial-garbage for weak-pointers, finalizers, and weak-hash-tables. The current version also only supports SBCL.

Disclaimer

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).

Supported Platforms

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.

Loading Sheeple

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)

Notes

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.

Features

  • 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 Metaobject Protocol (or Metasheep Protocol, if you will), similar to the one described in AMOP. The basics of the MOP are available and undocumented... the feature is still under development.

Planned features that have not yet been implemented include:

  • CLOS integration, including autoboxing of entire class hierarchies.

  • Support more than just SBCL

Using Sheeple

Vocabulary

  • Sheep - singular used to refer to a single Sheeple object.

  • Sheeple - plural used to refer to more than one sheep. Also used to refer to the project.

  • =T= - the root object, ancestor of all sheep objects.

  • =Dolly= - Ancestor of all actual sheep (the ones that aren't wolves, of course).

  • Hierarchy List - An ordered set containing the order of delegation for a particular sheep. The set is fetched using SHEEP-HIERARCHY-LIST

  • Ancestor - Any sheep that appears in the SHEEP-HIERARCHY-LIST for any given sheep, with sheep removed. This list only includes direct ancestors, not objects that could be considered 'cousins'.

  • Descendant - For a sheep X, any sheep Y whose hierarchy list will contain X, except Y itself.

  • Parent - A direct parent of a sheep, e.g. a sheep object present directly in a sheep's sheep-direct-parents list.

  • Child - A direct child of a sheep.

  • Sibling - Two sheeple that share the same hierarchy list are siblings

  • Buzzword - A Generic function. Holds pointers to all existing messages (methods)

  • Message - The message a particular buzzword gets across when given a set of arguments. Messages are defines for a specific set of args, and can be specialized on particular sheeple. Built-in types are supported, with the naming scheme =type-name=.

  • Wolf - A built-in lisp type.

  • Fleece - A wrapper for wolves that allows Sheeple to treat them as any other sheep -- specializing methods, cloning, etc.

  • Fleeced wolf - A wolf wrapped in a fleece. e.g. an autoboxed built-in type. All wolves are automatically fleeced by relevant functions.

  • =white-fang= - All fleeced wolves are descendants of =white-fang=, which is a clone of =dolly=.

  • Most lisp types have fleeces assigned to them, and they follow the =string= naming scheme.

API

== Sheep Cloning ==

  • Objects are prototypes. Objects are called sheep or sheeple. All sheep are children of =DOLLY=.

  • Sheeple can have multiple direct parents.

  • Ancestor ordering works the same way as it does with CLOS (first parent defined in CLONE has greater precedence than second.) Ancestors are ordered with a breadth-first left-to-right graph sorting algorithm.

  • The list of direct parents is dynamic. Parents can be added or removed at run time.

  • Sheeple does not have a universal addressing mechanism like functions and classes do. Instead, there are a number of built-in sheeple, including =T= and =DOLLY=, which can be addressed by symbol where useful, such as when setting one of them as a parent, or defining a talent. For ease of identification while working at the REPL, there is a nickname property in the metaobject, which can be accessed with SHEEP-NICKNAME.

  • Sheeple representing built-in types (known as fleeced wolves) correspond to all built-in Lisp types. CLOS autoboxing is not (yet) supported.

  • New sheeple are created by using the CLONE macro. CLONE works similar to DEFCLASS:

     (clone (parent1 parent2)
       ((property1 value1)
        (property2 value2 :property-option value))
       (:clone-option value)
       (:another-option value))
    
     (clone () ()) ;; also valid. Options are optional!
    
     (clone () ((foo 10)))
    
     (defvar *sample-sheep* (clone () ((my-property "Wheee" :manipulator get-my-property))))
    
  • An empty parent list when using CLONE will automatically clone =DOLLY=

Clone Options

  • The :deep-copy CLONE option copies over all values available to the new sheep into the sheep itself. This is useful for ensuring that other objects do not change an available property value. Using the :deep-copy option yields identical results.

  • :shallow-copy works like :deep-copy, except it only copies the new sheep's parents' direct-values locally, still relying on the hierarchy-list for other properties.

  • The :nickname CLONE option sets a nickname for the sheep metaobject, which is used when printing the object at the REPL. The nickname can be retrieved and changed at any time with the sheep-nickname manipulator function.

       SHEEPLE> (clone () () (:nickname "Johnny Bravo"))
       #<Standard Sheep AKA: Johnny Bravo {B777401}>
    

Property Options

  • :reader - creates a non-setfable reader message for this property. Trying to SETF signals a NO-MOST-SPECIFIC-MESSAGE error.

      (defparameter *sample-sheep* (clone () ((prop "value" :reader read-prop))))
    

    (read-prop sample-sheep) => "value" (setf (read-prop sample-sheep) "new-value" => NO-MOST-SPECIFIC-MESSAGE for (SETF READ-PROP)

  • :writer - creates a writer message, without creating a matching reader. The lambda-list for this message is: (new-value sheep-object).

    (defparameter *sample-sheep* (clone () ((prop "value" :reader read-prop :writer set-prop-to))))
    (set-me-to "new-value" *sample-sheep*) => "new-value"
    (read-prop *sample-sheep*) => "new-value"
    
  • :manipulator - Creates an manipulator, which is a reader that you can use SETF on.

    (defparameter *sample-sheep* (clone () ((prop "value" :manipulator prop))))
    (prop *sample-sheep*) => "value"
    (setf (prop *sample-sheep*) "new-value" => "new-value"
    
  • :cloneform - The form is compiled and saved. Any new children that are created will evaluate this form and set it as the property-value for that property, prior to setting any values defined in the new child's CLONE call. This form is not used as the value for the property of the current CLONE call -- a value must still be provided.

      (defvar *sample-sheep* 
         (clone () 
                ((account-number 
                  (incf *max-acc-nums*)
                  :reader account-number
                  :cloneform (incf *max-acc-nums*)))))
      (account-number *sample-sheep*) => 1
      (account-number (clone (*sample-sheep*) ())) => 2
    

Sheep Inspection

  • The functions DIRECT-PARENT-P, ANCESTOR-P, DIRECT-CHILD-P, and DESCENDANT-P return T for their corresponding checks.

Sheep Manipulation

  • The function ADD-PARENT receives a NEW-PARENT sheep, and a CHILD sheep. It pushes NEW-PARENT into CHILD's direct-parents if NEW-PARENT is not already there. If using ADD-PARENT would result in a cyclic hierarchy list, a SHEEP-HIERARCHY-ERROR condition is signaled, and the CHILD is unaffected. The new-parent is added to the front of the direct-parents list, like a stack push. Returns the CHILD object.

    (add-parent *mommy* *kiddo*) => <SHEEP {numbers}> (*kiddo*)
    (add-parent *kiddo's-descendant* *kiddo*) => ERROR: SHEEP-HIERARHY-ERROR
    
  • The function REMOVE-PARENT receives a PARENT sheep and a CHILD sheep.

    (remove-parent *daddy* *kiddo*) 
    => KIDDO object, without *daddy*'s direct-property values.
    

== Properties ==

  • Properties are key-value pairs, where key is a symbol.

  • One property may only have one value, although that value can be a collection of some sort.

  • Sheeple have direct properties and indirect properties

  • Direct properties are available directly from a specific sheep object.

  • Indirect property values come from the nearest parent in sheep-hierarchy-list that has a direct property value for that property.

  • Changing the value in the parent thus changes the value in the child.

  • A child may override the value set by one of its parents at any time by setting a direct property.

  • If an ancestor already sets a property, a descendant may not remove that property from itself, unless it removes that property from the ancestor. (all children are guaranteed to at least have NIL as the value for a property)

  • When accessing indirect properties, the hierarchy list ordering is obeyed.

  • The function PROPERTY-VALUE receives two arguments: a sheep object and a property-name. It returns the value of that property, and searches indirect properties. There is no user-level function for only getting the direct property value of a sheep. Signals a condition of type UNBOUND-PROPERTY if there is no binding, direct or indirect, for that property-name.

      (property-value *sample-sheep* 'foo)
    
  • The function (SETF PROPERTY-VALUE) sets a direct property on a sheep. If the property did not exist, it adds it, and it changes it if it did. Returns the new value.

      (setf (property-value *sample-sheep* 'foo) 'bar)
    
  • The function HAS-DIRECT-PROPERTY-P returns T if a direct property was set on sheep, and NIL otherwise.

      (has-direct-property-p *sample-sheep* 'foo) => T
    
  • The function HAS-PROPERTY-P returns T if the property is available to sheep, whether it's direct or indirect.

      (has-property-p *sample-sheep* 'foo) => T
    
  • The function REMOVE-PROPERTY receives a sheep and a property-name, and removes a direct property from sheep. Returns NIL if there was no direct property with that property-name set, and T if it removed an existing property.

      (remove-property *sample-sheep* 'foo) => T
    
  • The function WHO-SETS receives a SHEEP and a PROPERTY-NAME, and returns the sheep that sets a particular property visible to SHEEP, whether it be a direct-property or an indirect-property. If the property is unbound, it simply returns NIL.

      (who-sets *sample-sheep* 'foo) => NIL
    
  • The function AVAILABLE-PROPERTIES receives a SHEEP, and returns a list of available bound property keys for SHEEP. An empty list is returned if SHEEP has no available bound properties.

      (setf (property-value *sample-sheep* 'baz) 'quux)
      (available-properties *sample-sheep*) ==> (BAZ)
    

== Buzzwords/Messages ==

Buzzwords and Messages are very similar to CLOS' generic functions/methods. Anyone familiar with CLOS programming should have a fairly easy time wrapping their head around Sheeple's system.

Main interface

  • The DEFBUZZWORD macro is used to define a buzzword that will hold different messages. It accepts an optional documentation option. Buzzwords are automatically defined by defmessage, but the preferred and recommended style is to define buzzwords first. Not doing so signals STYLE-WARNING.

      (defbuzzword synergize (foo bar)
        (:documentation "Synergizes FOO and BAR, preparing them for the Next Generation"))
    
  • The DEFMESSAGE macro defines a message based on its arguments. Sheeple messages are left-weighted multimessages (by default) which can dispatch on multiple different arguments. The syntax follows closely with defmethod's specialized lambda-list syntax, except the specializers are actual objects instead of class names. An unspecialized item in the lambda list will default to dispatching on =dolly= for that lambda-list property. The basic format is:

      (defmessage message-name (plain-variable* (specialized-variable object)*) @body)
    

    Message definitions can also accept qualifiers. The standard method combination supports three qualifiers: :before, :after, and :around.

      (defmessage message-name :qualifier (...) body)
    

    This example illustrates usage of sheeple and messages:

      (defparameter sheep1 (clone () ()))
      (defparameter sheep2 (clone () ()))
    
      (defmessage do-it (foo)
         (print foo)) ;defaults to =dolly=
      (do-it "hey!") => hey! ;the message facility autoboxes regular lisp objects
    
      (defmessage do-it ((something sheep1)) 
         (format t "~a is sheep1. FYI" something))
      ;; lambda-list variables do not have to be the same across messages, 
      ;; I only do it here for convenience.
      (do-it sheep1) => #<Standard Sheep {BA28B89}> is the first sheep. FYI
    
      (let ((x 5))
     (defmessage do-it ((foo =number=)) 
       (* x foo))) ;this works inside closures, by the way
      (do-it 5) => 10
    
      (defmessage do-it ((foo 5)) ;any object can be dispatched on. Non-sheep are autoboxed.
     (declare (ignore foo)) ;declarations work
     (print "FIVE!! THIS IS VERY EXCITING!!1"))
      (do-it 5) => "FIVE!! THIS IS VERY EXCITING!!1"
    
      (defmessage do-it ((foo =float=)) (floatp float))
    
      (do-it 5.0) => T                                ; Sheeple has a CLOS-like hierarchy for
      (do-it 5) => "FIVE!! THIS IS VERY EXCITING!!1"  ; fleeced wolves.
      (do-it 6) => 12
    
      (defmessage synergize ((foo sheep1) (bar sheep2))
         (print "I got sheep1 first, then sheep2"))
    
      (defmessage synergize ((foo sheep2) (bar sheep1)) 
         (declare (ignore foo bar))
         (print "I got sheep2 first, then the first sheep."))
    
      (defmessage synergize ((foo =number=) (bar =number=)) 
         (+ foo bar))
      (synergize 3 5) => 8
      (synergize 4 "hey hey hey!") => ERROR: NO-MOST-SPECIFIC-MESSAGE
    
      (defmessage synergize ((foo =string=) (bar =string=))
         (concatenate 'string foo bar))
      (synergize "Hey " "Jude") => "Hey Jude"
    
      ;; :before and :after
      (defparameter =test-sheep= (clone () ((var "value" :manipulator var))))
      (var =test-sheep=) => "value"
    
      (defmessage var :before ((sheep =test-sheep=))
        (setf (property-value sheep 'var) "new-value"))
      (var =test-sheep=) => "new-value"
      (setf (var =test-sheep=) "old-value")
      (property-value =test-sheep= 'var) => "old-value"
      (var =test-sheep=) => "new-value"
    
      (defmessage var :after ((sheep =test-sheep=))
        (setf (property-value sheep 'var) "after-value"))
      (var =test-sheep=) => "new-value"
      (property-value =test-sheep= 'var) => "after-value"
    
  • UNDEFBUZZWORD completely undefines a buzzword, removing all messages and making the function unbound.

       (undefbuzzword synergize) => all definitions of synergize are removed, #'synergize is unbound
    
  • UNDEFMESSAGE accepts the same kind of arguments as defmessage, but reverses the process for a matching message.

       (undefmessage var :before ((x =test-sheep=)))
       (var =test-sheep=) => "value"
    
  • You can define messages on PRINT-SHEEP in order to change a particular sheep's printout...

     (defmessage print-sheep ((sheep my-special-sheep) stream)
       (format stream "~a is the most specialest sheep ever!" sheep))
    
  • Potential gotchas:

    • For the time being, CLOS objects and CLOS-defined types are boxed as =white-fang=, not as fleeced versions of the classes/objects.

    • If you define a buzzword, then clobber that buzzword with a defun or defgeneric, and redefine the buzzword using the same name, the warning about clobbering a regular function is not signaled.

Jump to Line
Something went wrong with that request. Please try again.