1 Lisp on Lines : The Missing Manual.
3 /Abstract/: Lisp on Lines is a Common Lisp based framework for rapid
4 development of data-driven applications. It is particularly useful
5 for producing Web based applications, but is also useful elsewhere.
10 Lisp on Lines (LoL) is a framework for rapid development of data-driven
11 applications, with a particular focus on web-based applications. It
12 builds on the UncommonWeb engine and Contextl, and uses CLOS and the
13 MOP extensively. Most of LoL can be used both at the REPL and through
14 the browser, offering many options for development and testing.
16 While the target audience for LoL is developers experienced with both
17 web technologies and common lisp, a good programmer with a little
18 experience in either should be able to pick things up fairly quickly.
22 LoL has a load of dependencies, which themselves depend on others,
23 etc. The best way to deal with this is to use [[http://common-lisp.net/project/clbuild/][clbuild]], a library
26 If you'd prefer to manage your libraries manually, the dependencies,
27 according to clbuild, are :
29 alexandria arnesi bordeaux-threads cl-base64 cl-fad cl-mime cl-ppcre
30 cl-qprint closer-mop contextl iterate lift local-time lw-compat
31 net-telent-date parenscript parse-number portable-threads puri rfc2109
32 slime split-sequence trivial-garbage ucw usocket yaclml
34 All libraries should be installed from version control where available.
36 * Describing the domain with the MAO protocol.
38 LoL uses a protocol it calls Meta-Attributed Objects, or MAO, as the
39 basis of its display mechanism. In MAO, we create context-aware
40 DESCRIPTIONs of objects, and those descriptions are used to generate
41 the display of the object itself. By having these external
42 descriptions change based on the context in which they are used, a few
43 generic components can come together to create complex interfaces.
46 Descriptions are a similar conceptually to classes. Every Lisp object
47 has one, and the root description that all descriptions inherit from
48 is known as T. FIND-DESCRIPTION is used to, well, find descriptions.
52 => #<DESCRIPTION T {B7B9861}>
55 ** Attributes and Properties
56 A description is a collection of ATTRIBUTEs, among other things. Each
57 attribute describes a part of an object, and any number of attributes
58 may or may not be active. The ATTRIBUTES function is used to find a
59 the list attributes that are both active and applicable in the current
63 (attributes (find-description t))
64 =>(#<ATTRIBUTE IDENTITY {BBC9691}>
65 #<ATTRIBUTE TYPE {BBC96A1}>
66 #<ATTRIBUTE CLASS {BBC96B1}>)
69 The functions DESCRIPTION-ATTRIBUTES, DESCRIPTION-ACTIVE-ATTRIBUTES
70 and DESCRIPTION-CURRENT-ATTRIBUTES return all the descriptions
71 attributes, Attributes that are currently active regardless of
72 context, and attributes that exist in the current context but may or
73 may not be active, respectively.
75 Attributes have properties, for example ATTRIBUTE-LABEL and
76 ATTRIBUTE-VALUE. By simply iterating through the attributes of a
77 described object, we can create a generic display for any lisp
78 object. This is very similar, and was inspired by the technique
79 outlined by Adrian Lienhard in [[http://www.adrian-lienhard.ch/files/mewa.pdf][MEWA: A Meta-level Architecture for
80 Generic Web-Application Construction_]].
83 For attribute properties to be useful, the description must be
84 associated with the object it is meant to describe.
86 The function FUNCALL-WITH-DESCRIBED-OBJECT takes care of setting up
87 the proper context. There is some syntax for it in the form of
88 WITH-DESCRIBED-OBJECT :
92 (let ((description (find-description t))
93 (object "Hello World"))
94 (with-described-object (object description)
95 (dolist (a (attributes description))
96 (format t "~@[~A: ~]~A~%"
98 (attribute-value a)))))
101 Type: (SIMPLE-ARRAY CHARACTER (11))
102 Class: #<BUILT-IN-CLASS SB-KERNEL::SIMPLE-CHARACTER-STRING>
107 FUNCALL-WITH-DESCRIBED-OBJECT binds two specials, *DESCRIPTION* and
108 *OBJECT*, to its arguments. Knowing this, we can shorten our code
109 somewhat. Later on we'll be far away from the lexical bindings of
110 description and object, so these special variables are essential.
112 Another reason for the *description* variable is that
113 WITH-DESCRIBED-OBJECT will use DESCRIPTION-OF to determine the
114 description if the DESCRIPTION argument is NIL
117 (with-described-object ("Hello World" nil)
118 (dolist (a (attributes *description*))
119 (format t "~@[~A: ~]~A~%"
121 (attribute-value a))))
123 Lets wrap that up in a function that we can re-use. LoL includes an
124 entire DISPLAY mechanism that is slightly more involved, but this
125 serves as an excellent example with not bogging us down in details.
128 (defun present (object &optional description)
129 (with-described-object (object description)
130 (dolist (a (attributes *description*))
131 (format t "~@[~A: ~]~A~%"
133 (attribute-value a)))))
138 MAO adds to MEWA the concept of dynamic context. By changing the
139 context in which an object is described, we combine and specialize the
140 generic displays, ultimately creating different views of our
141 objects. LoL uses ContextL extensively. Descriptions are contextl
142 layers, and attributes themselves are layered classes. Most of the
143 exported functions are layered methods, and the idea of dynamic
144 context-sensitivity is used throughout LoL. If you're not familiar
145 with contextl, don't worry, LoL mostly stands on its own. Still,
146 reading through the material on contextl won't hurt.
148 Descriptions may have different attributes dependant on what
149 description contexts (or layers) are currently active. Attributes
150 themselves might have different properties.
152 When an object is being described (using WITH-DESCRIBED-OBJECT), it is
153 also activated as a layer context. One can also activate/deactivate
154 contexts manually, using WITH-ACTIVE-DESCRIPTIONS and
155 WITH-INACTIVE-DESCRIPTIONS.
157 Hopefully a little code will make this more clear :
160 (present "Hello World")
163 Type: (SIMPLE-ARRAY CHARACTER (11))
164 Class: #<BUILT-IN-CLASS SB-KERNEL::SIMPLE-CHARACTER-STRING>
165 Simple character string
167 ;; Now we'll activate a built-in description, INLINE.
169 (with-active-descriptions (inline)
170 (present "Hello World"))
175 You can see that the behavior of PRESENT changed when the INLINE
176 context was activated. This is the key innovation that makes LoL so
177 useful. In the next chapter we'll create our own descriptions and
178 demonstrate this further.
180 * Defining and Using Descriptions
182 ** Defining a simple description
183 The basics of the MAO should now (hopefully) be clear, so lets start
184 using it. First, we'll create our very own description.
187 (define-description hello-world ()
188 ((title :value "Lisp on Lines Demo")
189 (identity :label "Message")
190 (length :label "Length" :function #'length)
191 (active-attributes :value '(title identity length))))
194 Descriptions are defined very much like CLOS classes, and are in fact
195 implemented that way, inheritance rules apply. The object returned
196 from FIND-DESCRIPTION is best described as a prototype-based
197 singleton. In other words, there is only one instance, and it inherits
198 attributes and properties from further up its hierarchy unless
199 specifically overridden.
201 Attributes can have any number of properties, (see the class
202 STANDARD-ATTRIBUTE), but the three most important are accessed via the
203 methods ATTRIBUTE-LABEL, ATTRIBUTE-VALUE and ATTRIBUTE-FUNCTION,and
204 named (in DEFINE-DESCRIPTION forms and elsewhere)
205 by the :label, :value, and :function keywords.
207 ATTRIBUTE-LABEL is simply a textual label that describes the
208 attribute. ATTRIBUTE-VALUE is defined to return the result of calling
209 ATTRIBUTE-FUNCTION with the object as its argument. If
210 ATTRIBUTE-FUNCTION is NIL, the value of the :value property is returned
213 In the example above, the IDENTITY and ACTIVE-ATTRIBUTES attributes
214 are inherited from T, and we are simply overriding the default
215 properties for our description. LENGTH and TITLE are specific to this
216 description. A look at src/standard-descriptions/t.lisp may be
217 instructive at this point.
219 Now, we can present our object using our new description.
222 (present "Hello World" (find-description 'hello-world))
231 ** Using descriptions as and with contexts.
233 A we mentioned earlier, when an object is being described, the
234 'description context' is also made active. On top of that, one can
235 define partial descriptions that are only active when other
236 description contexts have been activated.
238 We'll make a ONE-LINE description similar to the INLINE description
239 demonstrated earlier.
242 (define-description one-line ())
244 (define-description hello-world ()
245 ((identity :label nil)
246 (active-attributes :value '(identity)))
247 (:in-description one-line))
251 Here we've defined a new description, ONE-LINE, and a
252 context-sensitive extension to our HELLO-WORLD description. This
253 partial desription will be active only when in the context of a
254 one-line description. One can have attributes that only exist in
255 certain description contexts, and attributes can have different
259 (let ((message "Hello World!")
260 (description (find-description 'hello-world)))
261 (print :normal)(terpri)
262 (present message description)
263 (print :one-line)(terpri)
264 (with-active-descriptions (one-line)
265 (present message description)))
269 Message: Hello World!
278 By activating the description ONE-LINE, we've changed the context in
279 which our object is displayed. We can create any number of
280 descriptions and contexts and activate/deactivate them in any order.
282 Descriptions are implemented as ContextL 'layers', so if all
283 this seems weird, reading the ContextL papers might help.
285 ** T : The root of all descriptions.
287 Because all descriptions inherit from T, we can define contexts for T
288 and they will apply to every description. The INLINE description can
289 be found in standard-descriptions/inline.lisp, where we define
290 a desription for T in the context of the INLINE description :
293 ;; Defined by LoL in inline.lisp :
294 (define-description t ()
295 ((identity :label nil)
296 (active-attributes :value '(identity))
297 (attribute-delimiter :value ", ")
298 (label-formatter :value (curry #'format nil "~A: "))
299 (value-formatter :value (curry #'format nil "~A")))
300 (:in-description inline))}
304 The does for the LoL DISPLAY mechanism what ONE-LINE did for PRESENT,
305 only with more magic. By exetending T in this way, it's easy to create
306 contexts the redefine the behavior of LoL while still reusing the basics.
308 ** DESCRIPTION-OF : Permanently Associate a description with a class.
310 The LAYERED-FUNCTION DESCRIPTION-OF will return the description
311 associated with an object.
317 #<DESCRIPTION NULL {AA04F49}>
321 #<DESCRIPTION SYMBOL {AA04541}>
323 (description-of '(1 2 3))
325 #<DESCRIPTION CONS {AA04C29}>
331 * The DISPLAY Protocol
333 Our function, PRESENT, is very basic, though pretty powerful when
334 combined with descriptions and contexts. LoL includes a superset of
335 such functionality built-in.
337 The main entry point into this protocol is the DISPLAY
338 function. The signature for this functions is :
341 (display DISPLAY OBJECT &REST ARGS &KEY DEACTIVATE ACTIVATE &ALLOW-OTHER-KEYS)
344 The first argument, DISPLAY, is the place where we will display
345 to/on/in/with. It could be a stream, a UCW component, a CLIM gadget,
346 or anything else you might want to use.
348 One can specialize on this argument (though it's better to specialize
349 DISPLAY-USING-DESCRIPTION... more on that later) to use generic
350 descriptions to display objects in different environments.
352 The second argument is simply the object to be displayed. Here's a
360 Class:#<BUILT-IN-CLASS SYMBOL>
364 Package:#<PACKAGE "COMMON-LISP">
369 The two arguments specified in the lambda-list, ACTIVATE and
370 DEACTIVATE, are used to activate and deactivate description contexts in
371 the scope of the display function.
375 (display nil t :activate '(inline))
378 (with-active-descriptions (inline)
379 (display nil t :deactivate '(inline)))
383 Class:#<BUILT-IN-CLASS SYMBOL>
387 Package:#<PACKAGE \"COMMON-LISP\">
392 Any other keyword arguments passed will be used to set the value of an
393 attribute with a :keyword property, in the dynamic context of the
394 DISPLAY function call. Once such attribute, and a very useful one is
395 ACTIVE-ATTRIBUTES with its :attributes keyword :
399 (display t t :attributes '(class package))
401 Class:#<BUILT-IN-CLASS SYMBOL>
402 Package:#<PACKAGE "COMMON-LISP">
406 The properties of attributes that do not have a :keyword property can
407 also be set dynamically. Since :attributes is the :keyword property of
408 the ACTIVE-ATTRIBUTES attribute, the following form is equivalent to
412 (display t t :attributes '((active-attributes
413 :value (class package))))
415 Class:#<BUILT-IN-CLASS SYMBOL>
416 Package:#<PACKAGE "COMMON-LISP">
419 Setting the attributes this way is almost like creating an anonymous
420 description context... you can express just about anything you would
421 in a DEFINE-DESCRIPTION. Here's a more involved example :
424 (display t t :attributes `((identity :label "The Object")
425 (class :label "CLOS Class")
426 (package :value "COMMON LISP" :function nil)
427 (type :value-formatter
429 (format nil "Got a value? ~A" a)))))
433 CLOS Class:#<BUILT-IN-CLASS SYMBOL>
435 Type:Got a value? BOOLEAN
439 I hope that serves well to demonstrate the concepts behind LoL, as
440 there is no API documentation available at the moment... use the
444 * Automatic Descriptions for CLOS classes.
446 Lisp-on-Lines includes a compose-able metaclass, DESCRIBED-CLASS. It
447 can be combined with _any_ other metaclass without affecting the
448 behavior of that class. DESCRIBED-CLASS has been used with the
449 metaclasses provided by CLSQL, ROFL, Rucksack and UCW simply by
450 defining a class that inherits from both metaclasses.
452 DESCRIBED-CLASS creates a base description for the class, named
453 DESCRIPTION-FOR-<class>, and another description with the same name
454 as the class that has the previous description as a superclass. The
455 then defines a method on DESCRIPTION-OF that returns the second
458 LoL includes DESCRIBED-STANDARD-CLASS, which is subclass of
459 STANDARD-CLASS and DESCRIBED-CLASS. We'll use this to create a class
465 (first-name last-name company-name
466 date-of-birth phone fax email
467 address city province postal-code)
468 (:metaclass described-standard-class))
470 #<DESCRIBED-STANDARD-CLASS PERSON>
472 (display t (make-instance 'person))
474 First name:#<UNBOUND>
476 Company name:#<UNBOUND>
477 Date of birth:#<UNBOUND>
484 Postal code:#<UNBOUND>
488 ** Described CLOS objects an the EDITABLE description
490 The slots of an object are SETF'able places, and LoL takes
491 advantage of that to provide EDITABLE descriptions
492 automatically. When the EDITABLE description is active, and editor
493 will be presented. The REPL based editor is pretty basic, but still
494 useful. The HTML based editor will be described later.
498 (defun edit-object (object &rest args)
499 (with-active-descriptions (editable)
500 (apply #'display t object args)))
502 (let ((object (make-instance 'person)))
507 ;; What follows are prompts and the information i entered
513 Company name:The Tech Co-op
515 Date of birth:1978-07-31
521 Email:drewc@tech.coop
523 Address:s/v Kanu, Lower Fraser River
531 ;; And this is what was displayed.
535 Company name:The Tech Co-op
536 Date of birth:1978-07-31
539 Email:drewc@tech.coop
540 Address:s/v Kanu, Lower Fraser River
546 ** Extending the generated description
548 We mentioned earlier that DESCRIBED-CLASS creates two descriptions :
552 (find-description 'description-for-person)
554 #<DESCRIPTION DESCRIPTION-FOR-PERSON {D296DE1}>
556 (find-description 'person)
558 #<DESCRIPTION PERSON {ADFEDB1}>
560 (description-of (make-instance 'person))
562 #<DESCRIPTION PERSON {ADFEDB1}>
567 The reason for this is so we can redefine the description PERSON while
568 keeping all the generated information from DESCRIPTION-FOR-PERSON.
570 In this case, we will add an attribute, PERSON-AGE, that calculates
571 a persons age based on the data in the date-of-birth slot.
585 * Using Lisp-on-Lines for the Web.
587 LoL was developed, and is primarily used, for implementing
588 data-driven web applications. As such, it comes with a host of
589 features for doing just that.
591 LoL, by default, implements its web portion on top of the wonderful
592 UnCommon Web meta-framework. The LISP-ON-LINES-UCW ASDF system
593 should be loaded, as it provides the features we're going to