Sunday, March 6, 2011

Formlets and Loop

Just a quick update today.

First, I've pushed an update to the formlets project on github. It now supports input type file (which includes managing the enctype properly, displaying file inputs and providing a couple of basic predicate generators for validation). Check the project page, or the new wiki, both of which have slightly more robust documentation than you'll find here.

There's really no occasion to this, by the way. I try to be a self-centered realist in terms of design philosophy, so the only reason I added file fields here was that I finally found I needed them. It actually surprises me quite a bit that I got by for so long with only inputs, passwords, textareas and recaptchas, but there you have it. I'm in the middle of another project at work now though, so I may soon add options and dates. Don't hold your breath though.

Second, I've been figuring out loop for the past little while (and re-wrote a lot of the formlet internals with it, now that I've realized that it can basically do everything). The pieces that loop helped in are ones that would otherwise have to be expressed in terms of recursion and some intermediary variables. If you want to take a look at it in action, check out the "Validation related functions" section in this diff. Seven lines of loop saved me something like 12 lines of recursion and six lines of helper function. And not only that, but it's (in my opinion, obviously) much easier for a human reader to parse this way. I haven't learned the full loop syntax yet, and doubt I ever will, given its specification. It looks like loop itself is, without exaggeration, several times more complicated than the rest of Common Lisp combined. iterate doesn't seem to be much better in this regard, by the way. It seems to be loop with a few extra parens thrown in. But loop isn't hard to learn because it has too few parentheses, rather becuase it's mind-bendingly complicated.

In any case, I've found tutorials on both iterate and loop (as well as the CL cookbook loop entry and Seibels' treatment in PCL). The two things that I needed to know in order to make that formlets code work were either omitted, buried, or merely implied in each source. Specifically, I needed to iterate by two elements of a list, and I needed to be able to return a flat list that had double the elements of the input (collecting two elements per input element). Basically

'(:a 1 :b 2 :c 3 :d 4) => '(:a :b :c :d)
'(:a :b :c :d) => '(:a "A" :b "B" :c "C" :d "D")

That's very slightly beyond mapcar as far as I know, so the way it ended up getting written was a recursion. Which came out very complicated (not that the situation helped; this would have been relatively straightforward in regular code, but throw in two or three steps of macroexpansion, and it gets ugly fast). So, for my own future reference (and hopefully, for the benefit of anyone else that does a search on this), here's how you do it with loop.

;; Iterating by multiple elements
(defvar test-list '(:a 1 :b 2 :c 3 :d 4))
> TEST-LIST

(loop 
   for (key value) on test-list by #'cddr
   collecting key)
> (:A :B :C :D)

;; Collecting multiple elements
(setf test-list '(:a :b :c :d))
> (:A :B :C :D)

(loop 
   for key in test-list
   collecting key 
   collecting (string key))
> (:A "A" :B "B" :C "C" :D "D")

You can actually skip the "ing" in this case, writing the last two clauses as collect key collect (string key). There's also no requirement for making this a multi-line statement, I just feel that it's easier to read here.

EDIT: It's also been pointed out to me that the second one there can be written without loop as (mapcan (lambda (n) (list n (string n)) test-list).

Third, and no one other than me cares, so you may want to go read something interesting instead.

I know it's not very impressive yet, but keep in mind that I started off in the 35-45 range. Hopefully, I can crack 100 this year.

No comments:

Post a Comment