fb50e621 |
1 | Lisp on Lines : The Missing Manual. |
2 | |
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. |
6 | |
7 | |
8 | * Introduction |
9 | |
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. |
15 | |
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. |
19 | |
20 | * Installation |
21 | |
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 |
24 | management tool. |
25 | |
26 | If you'd prefer to manage your libraries manually, the dependencies, |
27 | according to clbuild, are : |
28 | |
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 |
33 | |
34 | All libraries should be installed from version control where available. |
35 | |
36 | * Describing the domain with the MAO protocol. |
37 | |
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. |
44 | |
45 | ** Descriptions |
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. |
49 | |
50 | #+BEGIN_SRC lisp |
51 | (find-description t) |
52 | => #<DESCRIPTION T {B7B9861}> |
53 | #+END_SRC |
54 | |
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 |
60 | context. |
61 | |
62 | #+BEGIN_SRC lisp |
63 | (attributes (find-description t)) |
64 | =>(#<ATTRIBUTE IDENTITY {BBC9691}> |
65 | #<ATTRIBUTE TYPE {BBC96A1}> |
66 | #<ATTRIBUTE CLASS {BBC96B1}>) |
67 | #+END_SRC |
68 | |
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. |
74 | |
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_]]. |
81 | |
82 | |
83 | For attribute properties to be useful, the description must be |
84 | associated with the object it is meant to describe. |
85 | |
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 : |
89 | |
90 | #+BEGIN_SRC lisp |
91 | |
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~%" |
97 | (attribute-label a) |
98 | (attribute-value a))))) |
99 | => |
100 | Hello World |
101 | Type: (SIMPLE-ARRAY CHARACTER (11)) |
102 | Class: #<BUILT-IN-CLASS SB-KERNEL::SIMPLE-CHARACTER-STRING> |
103 | |
104 | NIL |
105 | #+END_SRC |
106 | |
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. |
111 | |
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 |
115 | |
116 | #+BEGIN_SRC lisp |
117 | (with-described-object ("Hello World" nil) |
118 | (dolist (a (attributes *description*)) |
119 | (format t "~@[~A: ~]~A~%" |
120 | (attribute-label a) |
121 | (attribute-value a)))) |
122 | |
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. |
126 | |
127 | #+BEGIN_SRC lisp |
128 | (defun present (object &optional description) |
129 | (with-described-object (object description) |
130 | (dolist (a (attributes *description*)) |
131 | (format t "~@[~A: ~]~A~%" |
132 | (attribute-label a) |
133 | (attribute-value a))))) |
134 | #+END_SRC |
135 | |
136 | ** Contexts |
137 | |
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. |
147 | |
148 | Descriptions may have different attributes dependant on what |
149 | description contexts (or layers) are currently active. Attributes |
150 | themselves might have different properties. |
151 | |
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. |
156 | |
157 | Hopefully a little code will make this more clear : |
158 | |
159 | #+BEGIN_SRC lisp |
160 | (present "Hello World") |
161 | => |
162 | Hello World |
163 | Type: (SIMPLE-ARRAY CHARACTER (11)) |
164 | Class: #<BUILT-IN-CLASS SB-KERNEL::SIMPLE-CHARACTER-STRING> |
165 | Simple character string |
166 | |
167 | ;; Now we'll activate a built-in description, INLINE. |
168 | |
169 | (with-active-descriptions (inline) |
170 | (present "Hello World")) |
171 | => |
172 | Hello World |
173 | #+END_SRC |
174 | |
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. |
179 | |
180 | * Defining and Using Descriptions |
181 | |
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. |
185 | |
186 | #+BEGIN_SRC lisp |
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)))) |
192 | #+END_SRC |
193 | |
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. |
200 | |
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. |
206 | |
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 |
211 | directly. |
212 | |
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. |
218 | |
219 | Now, we can present our object using our new description. |
220 | |
221 | #+BEGIN_SRC lisp |
222 | (present "Hello World" (find-description 'hello-world)) |
223 | => |
224 | Lisp on Lines Demo |
225 | Message: Hello World |
226 | Length: 11 |
227 | |
228 | NIL |
229 | #+END_SRC |
230 | |
231 | ** Using descriptions as and with contexts. |
232 | |
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. |
237 | |
238 | We'll make a ONE-LINE description similar to the INLINE description |
239 | demonstrated earlier. |
240 | |
241 | #+BEGIN_SRC lisp |
242 | (define-description one-line ()) |
243 | |
244 | (define-description hello-world () |
245 | ((identity :label nil) |
246 | (active-attributes :value '(identity))) |
247 | (:in-description one-line)) |
248 | |
249 | #+END_SRC |
250 | |
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 |
256 | properties. |
257 | |
258 | #+BEGIN_SRC lisp |
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))) |
266 | => |
267 | :NORMAL |
268 | Lisp on Lines Demo |
269 | Message: Hello World! |
270 | Length: 12 |
271 | |
272 | :ONE-LINE |
273 | Hello World! |
274 | |
275 | NIL |
276 | #+END_SRC |
277 | |
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. |
281 | |
282 | Descriptions are implemented as ContextL 'layers', so if all |
283 | this seems weird, reading the ContextL papers might help. |
284 | |
285 | ** T : The root of all descriptions. |
286 | |
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 : |
291 | |
292 | #+BEGIN_SRC lisp |
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))} |
301 | |
302 | #+END_SRC |
303 | |
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. |
307 | |
308 | ** DESCRIPTION-OF : Permanently Associate a description with a class. |
309 | |
310 | The LAYERED-FUNCTION DESCRIPTION-OF will return the description |
311 | associated with an object. |
312 | |
313 | #+BEGIN_SRC lisp |
314 | |
315 | (description-of nil) |
316 | => |
317 | #<DESCRIPTION NULL {AA04F49}> |
318 | |
319 | (description-of t) |
320 | => |
321 | #<DESCRIPTION SYMBOL {AA04541}> |
322 | |
323 | (description-of '(1 2 3)) |
324 | => |
325 | #<DESCRIPTION CONS {AA04C29}> |
326 | |
327 | ;;etc |
328 | |
329 | #+END_SRC |
330 | |
331 | * The DISPLAY Protocol |
332 | |
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. |
336 | |
337 | The main entry point into this protocol is the DISPLAY |
338 | function. The signature for this functions is : |
339 | |
340 | #+BEGIN_SRC lisp |
341 | (display DISPLAY OBJECT &REST ARGS &KEY DEACTIVATE ACTIVATE &ALLOW-OTHER-KEYS) |
342 | #+END_SRC |
343 | |
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. |
347 | |
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. |
351 | |
352 | The second argument is simply the object to be displayed. Here's a |
353 | simple example : |
354 | |
355 | #+BEGIN_SRC lisp |
356 | (display t t) |
357 | => |
358 | T |
359 | Type:BOOLEAN |
360 | Class:#<BUILT-IN-CLASS SYMBOL> |
361 | Symbol |
362 | Name:T |
363 | Value:T |
364 | Package:#<PACKAGE "COMMON-LISP"> |
365 | Function:<UNBOUND> |
366 | ; No value |
367 | #+END_SRC |
368 | |
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. |
372 | |
373 | #+BEGIN_SRC lisp |
374 | |
375 | (display nil t :activate '(inline)) |
376 | => |
377 | "t" |
378 | (with-active-descriptions (inline) |
379 | (display nil t :deactivate '(inline))) |
380 | => |
381 | "T |
382 | Type:BOOLEAN |
383 | Class:#<BUILT-IN-CLASS SYMBOL> |
384 | Symbol |
385 | Name:T |
386 | Value:T |
387 | Package:#<PACKAGE \"COMMON-LISP\"> |
388 | Function:<UNBOUND>" |
389 | |
390 | #+END_SRC |
391 | |
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 : |
396 | |
397 | #+BEGIN_SRC lisp |
398 | |
399 | (display t t :attributes '(class package)) |
400 | => |
401 | Class:#<BUILT-IN-CLASS SYMBOL> |
402 | Package:#<PACKAGE "COMMON-LISP"> |
403 | |
404 | #+END_SRC |
405 | |
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 |
409 | the previous : |
410 | |
411 | #+BEGIN_SRC lisp |
412 | (display t t :attributes '((active-attributes |
413 | :value (class package)))) |
414 | => |
415 | Class:#<BUILT-IN-CLASS SYMBOL> |
416 | Package:#<PACKAGE "COMMON-LISP"> |
417 | #+END_SRC |
418 | |
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 : |
422 | |
423 | #+BEGIN_SRC lisp |
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 |
428 | ,(lambda (a) |
429 | (format nil "Got a value? ~A" a))))) |
430 | => |
431 | |
432 | The Object:T |
433 | CLOS Class:#<BUILT-IN-CLASS SYMBOL> |
434 | Package:COMMON LISP |
435 | Type:Got a value? BOOLEAN |
436 | |
437 | #+END_SRC |
438 | |
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 |
441 | source luke! |
442 | |
443 | |
444 | * Automatic Descriptions for CLOS classes. |
445 | |
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. |
451 | |
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 |
456 | description. |
457 | |
458 | LoL includes DESCRIBED-STANDARD-CLASS, which is subclass of |
459 | STANDARD-CLASS and DESCRIBED-CLASS. We'll use this to create a class |
460 | and its description. |
461 | |
462 | #+BEGIN_SRC lisp |
463 | |
464 | (defclass person () |
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)) |
469 | => |
470 | #<DESCRIBED-STANDARD-CLASS PERSON> |
471 | |
472 | (display t (make-instance 'person)) |
473 | => |
474 | First name:#<UNBOUND> |
475 | Last name:#<UNBOUND> |
476 | Company name:#<UNBOUND> |
477 | Date of birth:#<UNBOUND> |
478 | Phone:#<UNBOUND> |
479 | Fax:#<UNBOUND> |
480 | Email:#<UNBOUND> |
481 | Address:#<UNBOUND> |
482 | City:#<UNBOUND> |
483 | Province:#<UNBOUND> |
484 | Postal code:#<UNBOUND> |
485 | |
486 | #+END_SRC |
487 | |
488 | ** Described CLOS objects an the EDITABLE description |
489 | |
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. |
495 | |
496 | |
497 | #+BEGIN_SRC lisp |
498 | (defun edit-object (object &rest args) |
499 | (with-active-descriptions (editable) |
500 | (apply #'display t object args))) |
501 | |
502 | (let ((object (make-instance 'person))) |
503 | (edit-object object) |
504 | (terpri) |
505 | (display t object)) |
506 | |
507 | ;; What follows are prompts and the information i entered |
508 | |
509 | First name:Drew |
510 | |
511 | Last name:Crampsie |
512 | |
513 | Company name:The Tech Co-op |
514 | |
515 | Date of birth:1978-07-31 |
516 | |
517 | Phone:555-5555 |
518 | |
519 | Fax:555-5555 |
520 | |
521 | Email:drewc@tech.coop |
522 | |
523 | Address:s/v Kanu, Lower Fraser River |
524 | |
525 | City:Richmond |
526 | |
527 | Province:BC |
528 | |
529 | Postal code:V1V3T6 |
530 | |
531 | ;; And this is what was displayed. |
532 | |
533 | First name:Drew |
534 | Last name:Crampsie |
535 | Company name:The Tech Co-op |
536 | Date of birth:1978-07-31 |
537 | Phone:555-5555 |
538 | Fax:555-5555 |
539 | Email:drewc@tech.coop |
540 | Address:s/v Kanu, Lower Fraser River |
541 | City:Richmond |
542 | Province:BC |
543 | Postal code:V1V3T6 |
544 | #+END_SRC |
545 | |
546 | ** Extending the generated description |
547 | |
548 | We mentioned earlier that DESCRIBED-CLASS creates two descriptions : |
549 | |
550 | #+BEGIN_SRC lisp |
551 | |
552 | (find-description 'description-for-person) |
553 | => |
554 | #<DESCRIPTION DESCRIPTION-FOR-PERSON {D296DE1}> |
555 | |
556 | (find-description 'person) |
557 | => |
558 | #<DESCRIPTION PERSON {ADFEDB1}> |
559 | |
560 | (description-of (make-instance 'person)) |
561 | => |
562 | #<DESCRIPTION PERSON {ADFEDB1}> |
563 | |
564 | #+END_SRC |
565 | |
566 | |
567 | The reason for this is so we can redefine the description PERSON while |
568 | keeping all the generated information from DESCRIPTION-FOR-PERSON. |
569 | |
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. |
572 | |
573 | |
574 | |
575 | |
576 | |
577 | |
578 | |
579 | |
580 | |
581 | |
582 | |
583 | |
584 | |
585 | * Using Lisp-on-Lines for the Web. |
586 | |
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. |
590 | |
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 |
594 | discuss. |
595 | |
596 | |
597 | |
598 | |
599 | |
600 | |
601 | |
602 | |
603 | |
604 | |
605 | |
606 | |
607 | |
608 | |
609 | |