ailisp / flute

A beautiful, easilly composable HTML5 generation library in Common Lisp
64 stars 7 forks source link

An extended `&key` arglist in the function's definition could help autodoc #2

Open joaotavora opened 6 years ago

joaotavora commented 6 years ago

https://github.com/ailisp/flute/blob/3138f7f7c583602debc3bf252027aa4c8ab16ea4/src/flute.lisp#L133

Without ever having tried your library, which looks very good, I wonder if this macro could get (from the HTML5 spec) the keyword parameters most commonly required/optional for each element and code them as &key args. You would still &allow-other-keys and you could immediately ignore these args, but the big advantage is that you get to see this in the autodoc hint in SLY/SLIME.

This is something I wish other libraries had. Since yours is so new and clean, perhaps you could add it. I hope I am explaining myself.

ailisp commented 6 years ago

Thanks! Totally make sense and helps when i need to lookup which attributes are available for this tag. working on it.

ailisp commented 6 years ago

@joaotavora After i trying to add all keyword args here: https://github.com/ailisp/flute/tree/add-attr-for-auto-doc I find, unfortunately it's not possible to turn &rest attrs-and-children to &key ... &allow-other-keys because attrs are not cl keyword args -- for example (div :id "aa" :onclick "..." "This will be recorgnized as it's first child" "And this is second" (div "and this div as third child)). Just lookup hyperspec i can't have a lambda list with&rest after &key. If, I try &rest before &key:

(defun aa (&rest a &key b c &allow-other-keys)
    (list a b c))
CL-USER> (aa :b 3 :d 4 :e 5 "aaa")
; Evaluation aborted on #<SB-INT:SIMPLE-PROGRAM-ERROR "odd number of &KEY arguments" {1003E17BF3}>.
CL-USER> (aa :b 3 :d 4 :e 5 "aaa" "bbb")
((:B 3 :D 4 :E 5 "aaa" "bbb") 3 NIL)

Even number of children works, I can just use this a as attrs-and-children, but odd number will cause odd number of &KEY arguments error. And this also breaks the case if giving all attributes as a plist, like (div '(:id "a") "child") (This format is intended for quickly passing a programmatically calculated attributes, without using apply). Any ideas to work around this? Thank you!

joaotavora commented 6 years ago

ideas to work around this? Thank you!

Er, use a macro. :) What exactly are the advantages of functions here again?

ailisp commented 6 years ago

@joaotavora Hi, in short if these html elements were macros, they'll 1. have no knowledge of their type of arguments. 2. Write define new elements become write defmacros. 3. I'm planned to compile it to javascript to create single page application. You and other great lisp programmers may have solutions to these, but to solve all of them may be not worth the time to simply have element as functions.

Advantages in long example:

  1. flute supports:

    (div :id "a" "child") ; best for human write
    ;; and
    (div '(:id "a") "child") ; usually we just write the above one, but this is useful when '(:id "a") is a var of plist
    (div "another-child" "child")
    (div (button :class "btn" "child button") "child")
    ;; and 
    (div some-var "child")

    by checking type of first element at run time. In the second case, Macro div can't tell the difference if the second param is a attribute plist that given by a variable: (div (cons :id (format nil "elem-~a" (inc *nelem*)) *shared-css*) "child") or an element that created by some other form: (div (some-func-or-macro-create-button arg) "child") For both above they''re recognized by list. And similar for (div some-var "child") macro only know some-var is a symbol SOME-VAR. In other word, macro expands outside one first but this kind of check want attributes and children known before create parent container.

  2. This outside-in property of macros also making composite elements not very friendly, it makes all user defined elements to write like defmacro. For example:

    # just a special defun
    (define-element form-input (default name label type)
    (div :class "form-control"
     (label :for name label)
     (input :name name :id name :type (or type "text") :value default)))

    If everything is macro:

    # it's a special defmacro now, suppose this auto once-only for you
    (define-element form-input (default name label type)
    `(div :class "form-control"
        (label :for ,name ,label)
        (input :name ,name :id ,name :type (or ,type "text") :value ,default)))

    And after nested a few level of small user defined element to a high level one, it becomes mentally impossible for me (and probably, more error prone for you?) to think of these macros. Even in this small example i need to think should i use (or ,type "text") or ,(or type "text")? I had encounter this a lot when I was working on a course project: https://github.com/ailisp/music-app

  3. As i consult clojure/clojurescript, macros are only in clojure files and can be used in clojurescript. Not sure if there's some burden in have defmacro in a javascript runtime, so to keep define-element as defun may be safer.