Arc Forumnew | comments | leaders | submit | killerstorm's commentslogin

i find this LoL code object-obsessed and unreasonable verbose because of this: how many time you repeat message/message/message?

i never used LoL, but coding with UCW is PITA for me.

-----

9 points by drewc 5900 days ago | link

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 5900 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 5899 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?

-----


Common Lisp, ABCL-web(http://abcl-web.sourceforge.net/web-engine.html):

    (defun said ()
      (make-page "a" 
       (action-form (((:input :name "foo"))
                      (form-submit "s"))
        (make-page "b"
          (action-link "click here"
             (make-page "c"
                (html (:princ "you said: " (getf form-data :foo))))))))
pretty much same, modulo shortcut functions, but i'd say it's easier to understand code in ABCL-web because code flow matches control flow.

-----

2 points by ryantmulligan 5899 days ago | link

I like the control flow better than the arc example.

-----

2 points by pg 5899 days ago | link

Defun defines something that works in urls?

-----

2 points by killerstorm 5899 days ago | link

no, direct accessibility by URL was not in original requirements, so i didn't bother with it. in ABCL-web there is one entry point and other pages are linked via action-link from it. so if i rename said to start-page it will work directly.

newer version of ABCL-web i'm currenly working on supports publishing functions by URLs, so i could write "(defpage said.." with it.

-----

2 points by pg 5898 days ago | link

Technically it was in the original requirements, which were to translate the Arc code. Would there be a way to do that in ABCL? If not I look forward to seeing it in the new version.

-----

1 point by killerstorm 5898 days ago | link

everything is possible, of course, but there's no _pretty_ way to define new named entry point. with code I currently have at hands one needs to populate table manually:

    (setf (gethash "said" *views*)
          (view-definition (action-form ...
because it was made as an experiment to submit/publish functions via web, and so there's no macro like defpage to do this.

i'm actually planning to finalize this stuff and release it as live demo -- so people can try programming it online -- soon.

-----