The magic incantations to get things started:
And the curse of tradition:
(defwethod hello () (render)) (defview hello () (:html (:body "Hello World."))) (defurlmap hello-map ("hello" :wethod hello)) ;; install the urlmap `hello-map' with root url-prefix "root", ;; so every path in hello-map is accessible via /root/<path> (attach-urlmap "root" 'hello-map)
render is intuitively like
call-next-method. It calls
the view rendering for the wethod with the same arguments provided
to the wethod itself. The view for
hello responds to the
request with html string.
A urlmap map is a set of url-prefixes to functions. In this simple case,
hello is mapped to the prefix "hello", and the
hello-urlmap itself is mapped to "root", yielding the
overall path "/root/hello" to access the wethod
defwethod is like defmethod, it allows specialization of its
arguments. Let's define some foos:
(defwethod foo (a b) (render-html (:html (list a b)))) (defwethod foo ((a number) b) (render-html (:html `((number ,a) (t ,b))))) (defurlmap hello-map ("hello" :wethod hello) ("foo" :wethod foo))
Each of these
foo's is accessible through a unique url-pattern.
We can see the current active url-patterns by calling
(get-effective-urls 'hello-map) => ((:wethod (:url "/hello" "^/hello") :dispatch #<standard-generic-function hello> :discriminators (nil) :wethod-urls (("" "^$"))) (:wethod (:url "/foo" "^/foo"):dispatch #<standard-generic-function foo> :discriminators ((number t) (t string) (t t)) :wethod-urls (("/number_:arg1/:arg2" "^/number_([^/]+)/([^/]+)$") ("/:arg1/string_:arg2" "^/([^/]+)/string_([^/]+)$") ("/:arg1/:arg2" "^/([^/]+)/([^/]+)$"))))
Between each pairs of "/" is one url-argument, with the very first "_" used to separate the type-specifier from the value of the url-argument.
First, see how the generic function
hello is associated with
the prefix "/hello", but because it doesn't take any arguments, this
prefix uniquely identifies
hello. The url-pattern for
foo is more complicated. Its generic function is associated
with the prefix "/foo", and the rest of the request url is used to
determine with what arguments of what types to apply to foo.
"/number_:arg1/:arg2" says, the first argument is a number, and the
second argument has unspecified type. The generic function
^wisp-arg is used to translate a url-argument into its proper
type before applying it to
foo. In the case of a number,
parse-integer is used. In the case of an unspecified type, the
url-arg is passed in as a string.
^wisp-arg is easily
extended to convert an object-id (id as a string) into an object of
some type stored in the elephant object database.
Let's try some foo. Before that, we've modified
we need to reattach it before it comes into effect. This is
(attach-urlmap "root" 'hello-map)
deform deforms an html form so that
read-form can be
used within a wethod to prompt for user input. When the form returns,
the wethod continues from the continuation.
Let's try making a simple stack calculator.
(deform stack-calculator (stack transcript) ;; The fields of the form. (+ - * / push new-number) ;; When user submits the form, the following expression is evaluated ;; and the value(s) returned to the caller's continuation. (let ((op (^symbol (find-if-not #'null (list + - * / push))))) (if push (list op (cons (parse-integer new-number) stack)) (list op stack))) ;; outputs the html for the form. (:input :name new-number) (:input :name push :type 'submit :value 'push) (:br) (:input :name + :type 'submit :value '+) (:input :name - :type 'submit :value "-") (:input :name * :type 'submit :value '*) (:input :name / :type 'submit :value '/) (:div :id 'stack :style (:border 1px solid $FF0000 :width 200px) (dolist (number stack) (html number (:br)))) (:div :id 'transcript :style (:border 1px solid $00FF00 :width 200px) (dolist (cmd transcript) (html cmd (:br))))) (defwethod calculator-loop () (loop with transcript for (op stack) = ;; `read-form' will output the html for the form, save the continuation, ;; and return immediately from `calculator-loop'. When the user submits the ;; form, the url-handler calls the continuation. ;; ;; To the programmer, it appears as though read-form returns (list op stack). (read-form 'stack-calculator nil nil) then (read-form 'stack-calculator stack transcript) do (if (eql op 'push) (push (list 'push (car stack)) transcript) (let ((result (funcall (symbol-function op) (second stack) (first stack)))) (push (format nil "~S => ~S" (list op (second stack) (first stack)) result) transcript) (setf stack (cons result (nthcdr 2 stack))))))) (defurlmap calculator ;; maps the url /calc to the wethod calculator-loop ("" :wethod calculator-loop)) (attach-urlmap "calc" 'calculator)
For this to work, we need the following voodoo:
(defurlmap wisp-sys ;; call-from-k takes the string in place of `:k-id' as its (single) argument. ("form-k/:k-id" :handler call-form-k)) (attach-urlmap "wisp-sys" 'wisp-sys)
The biggest benefit of the use of continuation based forms is that the program "remembers" the state of a computation, without the programmer having to explicitly do so. The main drawbacks are:
Continuation, in the context of web-programming, is mainly used to deal with the problem of window duplication and back button in an interactive web interface. AJAX tackles the problem in a different way, by using an event-driven model, thus never really leaving the page. Continuations are useful when the designer in the interest of accessibility has to forego AJAX. Another desirable property of continuation-based applications is that program states are represented by bookmarkable URLs.
How Deform Works
(deform stack-calculator (stack transcript) (+ - * / push new-number) (let ((op (^symbol (find-if-not #'null (list + - * / push))))) (if push (list op (cons (parse-integer new-number) stack)) (list op stack))) ... )
stack-calculator is parameterizable by two arguments,
stack and transcript. This allows
stack-calculator by passing in the current
stack, and the transcript for input history. Like this:
(read-form 'stack-calculator stack transcript)
The form allows 6 inputs, (+ - * / push new-number). When the user submits the form, these symbols are bound to the string values of the corresponding input fields. The form:
(let ((op (^symbol (find-if-not #'null (list + - * / push))))) (if push (list op (cons (parse-integer new-number) stack)) (list op stack)))
has access to (+ - * / push new-number). This form is evaluated, and the value passed to the suspended continuation:
for (op stack) = (read-form 'stack-calculator nil nil) then (read-form 'stack-calculator stack transcript) do ...
so (op stack) in
calculator-loop is destructurally bound to the
value returned by
stack-calculator. The loop body continues
with new values of op and stack, updating the transcript to reflect
the newly received input, and finally looping back to the beginning
stack-calculator again to prompt for new input. Note
(read-form 'stack-calculator stack transcript)
now uses the new modified value for transcript to call
Fancy Layout with DOJO
DOJO makes your life easier at the cost of having non-comformant HTML. Oh well.
(defwethod test-dojo () (render-view 'layout)) (defview layout () (:html (:header (:js (= djConfig (object isDebug false))) (:js-src /src/dojo/dojo.js) (:js-require dojo.widget.*) (:css ((:and html body) :margin 0 :padding 0 :overflow hidden :width 100% :height 100%))) (:body (:layout-div :child-priority "none" :style (:width 80% :height 300px) (:top :style (:background red) "top") (:bottom :style (:background $000000 :color $ffffff) "bottom") (:left :style (:background $444444 :color $ffffff) "left") (:right :style (:background $888888)"right") (:top :style (:background $cccccc)"top 2") (:right :style (:background green :color $ffffff) "right") (:bottom :style (:background blue)"bottom 2") (:left :style (:background yellow :color black) "left") (:client :style (:text-align center)"How about 42?"))))) (defurlmap test-dojo ("test" :wethod test-dojo)) (attach-urlmap "dojo" 'test-dojo)
Now try, http://localhost:2002/dojo/test
:layout-div is the dojo layout container widget. It is a macro defined
deftag allows lambda-list of the form:
(<required-arg>* [[&key <key-arg>*]] [[&other-keys <symbol>]] [[&rest <symbol>]])
This lambda-list differs from the original lambda-list in that:
This is called the
rkr-lambda-list, where rkr stands for
"required-key-rest". It allows some required arguments, an arbitrary
number of keyword argument pairs, and the first non-keyword value and
whatever follows in the argument list are collected into &rest. It
makes defining new html tag very easy. Let's see how
(deftag :layout-div (&key child-priority &other-keys others &rest body) (flet ((make-pane (child) (let ((align (car child))) (if (find align '(:top :bottom :left :right :client :flood)) `(:pane :layout-align ,(^string align) ,@(cdr child)) child)))) `(:div :dojoType "LayoutContainer" ,@(when child-priority `(:layout-child-priority ,child-priority)) ,@others ,@(mapcar #'make-pane body))))
It's used like this:
(:layout-div :child-priority "none" :style (:width 80% :height 300px) (:top :style (:background red) "top") (:bottom :style (:background $000000 :color $ffffff) "bottom") ...)
Note 3 things:
:layout-divbut is captured by &other-keys others. It is spliced into the :div that actually implements
:layout-div. Like so:
`(:div :dojoType "LayoutContainer" ... ,@others ...)
In short, Wispy Lisp is very much a student project.
This marks the end of SoC2006, and the beginning of Wispy Lisp. I am grateful for this great opportunity afforded to me by Google and LispNYC.
Thanks LispVAN its love for lisp, when the world raves on about Java, C++, PHP, and other monstrocities.
Thank you, Marco, for helping me along the way, and allowing me the greatest freedom possible. Best wishes.
This document was generated by howard on August, 24 2006 using texi2html 1.76.