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