Arc Forumnew | comments | leaders | submitlogin
9 points by drewc 2462 days ago | link | parent

I would never write UCW/LoL code like this.. it's not at all in the style of a UCW application.. but the challenge was to use what was available in the language.

UCW is component based by default, so quite verbose for a small program like this indeed. It's not hello-world optimized. Having said that, there are a number of things we could do to optimize things for this particular example. It's all lisp right?

     ;;;;    Still using components.
     ;;;;    This, while it meets the spec, is semantically quite 
     ;;;;    differrent than the arc version, IIUC.
       (defvar *is-link* nil)
       (defun/cc pr (message args &optional (linkp *is-link*))
         (apply 'call-component *component* 'message 
                    :message (apply 'format nil message args)
                    :linkp *is-link*))
       (defmacro w/lnk (&body body)
         `(let ((*is-link* t)) ,@body))
       (defmacro aform (action &body body)
         `(<lol:form :action ,action ,@body))
       (define-symbol-macro _ (message *component*))
       (defcomponent message (info-message) ()
         ((is-link? :accessor link-p :initarg :linkp))
         (:render :around ((m message))
            (if (linkp m)
                 (<lol:a :action (answer m) (call-next-method))
                 (call-next-method))))
 
       ;;;; finally:
       (defcomponent said (message) ()
        (:render ((said said))
          (aform (progn (w/lnk (pr "click here")) (pr "you said ~A" _))
            (<lol:input :accessor _)
            (<:submit))))
That meets the spec, and the code itself looks very similar, but it's not at all doing what the arc version is doing. UCW does its rendering via components by default, hence my calls to MESSAGE every time i wanted to output. This is really cheating, and would require a lot of changes to keep up with further examples in arc.

As luck would have it, UCW is really quite flexible. In arc, we're just using call/cc directly, without the component architecture present in other continuation based frameworks (seaside, ucw. weblocks). It is trivial to remove the component stack and just render the continuations directly.

    ;;;; * LoL arc 
    ;;;;    This is a minimal 'arc-like' framework build on UCW.
    (defclass arc-action (basic-action) 
       ((renderer :initarg :body)) 
        (:metaclass action-class))
    (defmethod render ((action arc-action)) 
       (funcall (slot-value action renderer))
    (defmethod call-render ((action basic-action)) a s f)
       (handle-raw-request  (:with-network-output *standard-output*)
         (render action)))
    (defmacro defop (name &body renderer)
       `(progn 
            (defclass ,name (arc-action) () 
              (:metaclass action-class)
              (:default-initargs :body (lambda () ,renderer)))))
    (defun arg (name)
      (get-parameter (context.request *context*) name))
    (defmacro w/lnk (action &rest txt)
      `(<lol:a :action ,action :action-class arc-action (<:as-html txt))
    (defmacro aform (action &body body)
    `(<lol:form :action ,action :action-class arc-action ,@body))
    (defun input (name &optional value)
     (<:input :name name :value value))
    (defun submit () (<:submit))
    (defun pr (&rest strings)
     (print (apply 'concatenate 'string 'strings))

    ;;;; * Finally, we can re-create the arc example in CL
    ;;;;   using UCW/LoL. I'm not sure what 'req and '_ are 
    ;;;;   are for in the original, so i've omitted them.

    (defop said 
      (aform (w/lnk (pr "you said: " (arg "foo"))
                    (pr "click here"))
        (input "foo")
        (submit))
w00t! I do believe that is shorter than the ARC version :).

EDIT: oops .. figured out what the '_ is doing and it's absolutely needed. In Arc we are using a closure to capture the state of the world. In CL, special variables and the lack of [] make that a little more difficult/verbose. Rather than re-write my AFORM and either make a [] reader macro or code-walk AFORM, i'll just cheat a little and use an object to store params.

Objects and Closures .. same thing right?

    ;;;;We need to add a slot to the 'op' in order to hold the 
    ;;;; parameters, and make sure '_ is bound to the op.

    (defvar _)
    (defclass arc-op (arc-action)
      ((args :accessor args :initform (make-hash-table :test #'equal))
    (defmethod render :around ((op arc-op))
      (let ((_ op)) (call-next-method)))

    ;;;; Make sure the 'arg' machinery works with this new slot 
    ;;;; rather than the get/post params, which was the whole point.

    (defun arg (op name)
      (gethash name (args op) (error "No arg ~A found in ~A" name op)))
    (defun input (name &optional (val ""))
     (<:input :type "text" 
                   :name (register-callback
                               (lambda (new-val)
                                 (setf (gethash name (args _)) new-val)))
                   :val val))

    ;;;; And this time our example will really work!
    ;;;; Having though about it for a while, 'req is
    ;;;; Most likely the current request. 
    ;;;; In CL we just bind a special, *context*.
    ;;;; Arc lacks special variables AFAIK, so you need 
    ;;;; 'req.  Sombody please correct me if i'm wrong.

    (defop said 
      (aform (w/lnk (pr "you said: " (arg _ "foo"))
                    (pr "click here"))
        (input "foo")
        (submit))
edit2: explained differences in how _ works in arc and in the CL versions.


4 points by killerstorm 2462 days ago | link

yes, it's lisp, so you can do anything with shortcut function/macros, but what matters IMHO is traditional style of doing things.

i'm using UCW for more-or-less usual applications, and find it's not optimal for them either -- it's too hard to figure out how to do stuff, it's bloated, buggy and fragile.

maybe it's "optimal" for some other sort of applications, but i'm yet to see them.

i believe the problem is CLOS. it's tempting to do stuff in "extensible way", but it appears it gets extensible mostly in the way no one needs, and it same time it introduces huge complexity overhead and confusion. arcane method precendence rules instead of function call flow, object slots acting like global variables instead of function parameters. CLOS is evil.

-----

5 points by drewc 2461 days ago | link

I don't use the 'UCW-AJAX' component library, which i agree is 'bloated, buggy and fragile'. OTOH, The core of UCW (the dispatcher/action mechanism) is very lean, quite stable, and has a comprehensive test suite that ensures what bugs there are don't affect me. It is this core i use in LoL.

But, if you think CLOS is evil and there is something arcane about the precendence rules, you're a lost cause anyway. "object slots acting like global variables instead of function parameters" ... what does that even mean? :P

My last example uses absolutely no CLOS explicitly once the core language is defined, and is identical in almost every way to the arc version. It is through the miracle of CLOS and the MOP that i can so easily change the functionality of something like UCW, in about 5 minutes, to create a PLT-style webserver.

You may find CLOS confusing, but when writing massive lisp applications with constantly changing requirements on teams of 50+ programmers, i've found having an object system that is malliable enough to take the application anywhere, yet that is based on a few simple principles that anybody who has read the MOP understands, is something i would not trade for anything.

Our experiences obviously differ significantly. I never had any major difficulties figuring out UCW and CLOS... have you read Keene, AMOP or any other books that cover OO design in lisp? Maybe they'll help you 'figure out how to do stuff'. AMOP changed the way i view software design... i really recommend it.

Otherwise, have you considered PLT or even (gasp) arc? If you find CLOS confusing, you may be better off going with a simpler approach. If you don't need the component architecture, and you don't like CLOS, why would you use UCW in the first place?

-----