Tutorial

hello world

The magic incantations to get things started:

 
(asdf :wispylisp)
(in-package :wisp-mvc) 
;; start aserve
(start :port 2002)
;; easier aserve debug
(net.aserve::debug-on :notrap)
;; html,css, and javascript depend on :invert
(setf (readtable-case *readtable*) :invert)

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, the wethod hello is mapped to the prefix "hello", and the urlmap hello-urlmap itself is mapped to "root", yielding the overall path "/root/hello" to access the wethod hello.

Now, http://localhost:2002/root/hello

defwethod

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:

 
(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 hello-map, so we need to reattach it before it comes into effect. This is unfortunate.

 
(attach-urlmap "root" 'hello-map)

http://localhost:2002/root/foo/arg1/arg2
http://localhost:2002/root/foo/number_314159/arg2

deform

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)

10
(push 10)

35
10
(push 35)
(push 10)

350
(* 10 35) => 350
(push 35)
(push 10)

90
15
350
(push 90)
(push 15)
(* 10 35) => 350
(push 35)
(push 10)

-75
350
(- 15 90) => -75
(push 90)
(push 15)
(* 10 35) => 350
(push 35)
(push 10)

275
(+ 350 -75) => 275
(- 15 90) => -75
(push 90)
(push 15)
(* 10 35) => 350
(push 35)
(push 10)

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:

  1. Indiscrimination - the continuation saves the whole runtime environment. By abstracting the state-tracking problem away, the programmer loses the power to select only the relevant data to save.
  2. Reaping - As for now, there's no way to reap dead continuations. In the future, there will be the concept of "continuation commit", which would invalidate the chain of continuations leading to the commit point. Or even a simple timeout based reaper should work.

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.

It is interesting to note that the DOJO javascript toolkit has support for back button and bookmarkable AJAX event. This essentially reintroduces all the problems implied by the statelessness of the web-protocol. So again, continuation can be useful in this context.

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

The form stack-calculator is parameterizable by two arguments, stack and transcript. This allows calculator-loop to communicate to 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 and call stack-calculator again to prompt for new input. Note that

 
(read-form 'stack-calculator stack transcript)

now uses the new modified value for transcript to call stack-calculator.

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

deftag :layout-div is the dojo layout container widget. It is a macro defined with deftag. 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:

  1. &optional is not allowed.
  2. &key comes before &rest, but the key args don't appear in &rest.
  3. &other-keys collects the keyword arguments not specified in &key.

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:layout-div is implemented:

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

  1. :child-priority "none" is a keyword argument pair captured by &key child-priority.
  2. :style (:width 80% :height 300px) is a keyword argument pair not explicitly specified by :layout-div but is captured by &other-keys others. It is spliced into the :div that actually implements :layout-div. Like so:
     
    `(:div :dojoType "LayoutContainer"
    	 ...
    	 ,@others
             ...)
    
  3. (:top :style (:background red) "top") is the first form that breaks the keyword form keyword form ... pattern, therefore, it, and the forms that follow, are collected into &rest.

Inconclusion

This tutorial demostrates some functionalities of Wispy Lisp. Although Wispy Lisp can generate arbitrary javascript and css, their integration with the rest of the framework is rather halfbaked. The use of elephant for object persistence has great promises, and is worth investigating further.

In short, Wispy Lisp is very much a student project.

Ack

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.