# HG changeset patch # User Steve Losh # Date 1474380045 0 # Node ID 500ef047ae2c025f55c23ee0c02240be9fb4013d # Parent db148e22d042c004c057fb8208746165062fe9f8 Averaging diff -r db148e22d042 -r 500ef047ae2c content/blog/2016/09/iterate-averaging.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/blog/2016/09/iterate-averaging.html Tue Sep 20 14:00:45 2016 +0000 @@ -0,0 +1,151 @@ + {% extends "_post.html" %} + + {% hyde + title: "Customizing Common Lisp's Iterate: Averaging" + snip: "Don't loop, iterate!" + created: 2016-09-20 14:00:00 + %} + + {% block article %} + +When I first started learning Common Lisp, one of the things I learned was the +[loop macro][loop]. `loop` is powerful, but it's not extensible and [some +people find it ugly][lol]. The [iterate][] library was made to solve both of +these problems. + +Unfortunately I haven't found many guides or resources on how to extend +`iterate`. The `iterate` manual describes the macros you need to use, but only +gives a few sparse examples. Sometimes it's helpful to see things in action. + +I've made a few handy extensions myself in the past couple of months, so +I figured I'd post about them in case someone else is looking for examples of +how to write their own `iterate` clauses and drivers. + +This first post will show how to make a `averaging` clause that keeps a running +average of a given expression during the loop. I've found it handy in a couple +of places. + +[loop]: http://www.gigamonkeys.com/book/loop-for-black-belts.html +[iterate]: https://common-lisp.net/project/iterate/ +[lol]: /media/images{{ parent_url }}/loop-macro.jpg + +[TOC] + +## End Result + +Before we look at the code, let's look at what we're aiming for: + + :::lisp + (iterate (for i :in (list 20 10 10 20)) + (averaging i)) + ; => + 15 + + (iterate (for l :in '((10 :foo) (20 :bar) (0 :baz))) + (averaging (car l) :into running-average) + (collect running-average)) + ; => + (10 15 10) + +Simple enough. The `averaging` clause takes an expression (and optionally +a variable name) and averages its value over each iteration of the loop. + +## Code + +There's not much code to `averaging`, but it does contain a few ideas that crop +up often when writing `iterate` extensions: + + :::lisp + (defmacro-clause (AVERAGING expr &optional INTO var) + "Maintain a running average of `expr` in `var`. + + If `var` is omitted the final average will be + returned instead. + + Examples: + + (iterate (for x :in '(0 10 0 10)) + (averaging x)) + => + 5 + + (iterate (for x :in '(1.0 1 2 3 4)) + (averaging (/ x 10) :into avg) + (collect avg)) + => + (0.1 0.1 0.13333334 0.17500001 0.22) + + " + (with-gensyms (count total) + (let ((average (or var iterate::*result-var*))) + `(progn + (for ,count :from 1) + (sum ,expr :into ,total) + (for ,average = (/ ,total ,count)))))) + +We use `defmacro-clause` to define the clause. Check [the iterate manual][man] +to learn more about the basics of that. + +The first thing to note is the big docstring, which describes how to use the +clause and gives some examples. I prefer to err on the side of providing *more* +information in documentation rather than less. People who don't need the +hand-holding can quickly skim over it, but if you omit information it can leave +people confused. [Your monitor isn't going to run out of ink][ink] and [you +type fast (right?)][typing] so be nice and just write the damn docs. + +Next up is selecting the name of the variable for the average. The `(or var +iterate::*result-var*)` pattern is one I use often when writing `iterate` +clauses. It's kind of weird that `*result-var*` isn't external in the `iterate` +package, but this idiom is explicitly mentioned in the manual so I suppose it's +fine to use. + +Finally, we *could* have written a simpler version of `averaging` that just +returned the result from the loop: + + :::lisp + (defmacro-clause (AVERAGING expr) + (with-gensyms (count total) + `(progn + (for ,count :from 1) + (sum ,expr :into ,total) + (finally (return (/ ,total ,count)))))) + +This would work, but doesn't let us see the running average during the course of +the loop. `iterate`'s built-in clauses like `collect` and `sum` usually allow +you to access the "in-progress" value, so it's good for our extensions to +support it too. + +[man]: https://common-lisp.net/project/iterate/doc/Rolling-Your-Own.html#Rolling-Your-Own +[ink]: http://www.bash.org/?105201 +[typing]: https://steve-yegge.blogspot.is/2008/09/programmings-dirtiest-little-secret.html + +## Debugging + +This clause is pretty simple, but more complicated ones can get a bit tricky. +When writing vanilla Lisp macros I usually end up writing the macro and then +`macroexpand-1`'ing a sample of it to make sure it's expanding to what I think +it should. + +As far as I can tell there's no simple way to macroexpand an `iterate` clause on +its own. This is really a pain in the ass when you're trying to debug them, so +I [hacked together][mxi] a `macroexpand-iterate` function for my own sanity. +It's not pretty, but it gets the job done: + + :::lisp + (macroexpand-iterate '(averaging (* 2 x))) + ; => + (PROGN + (FOR #:COUNT518 :FROM 1) + (SUM (* 2 X) :INTO #:TOTAL519) + (FOR ITERATE::*RESULT-VAR* = (/ #:TOTAL519 #:COUNT518))) + + (macroexpand-iterate '(averaging (* 2 x) :into foo)) + ; => + (PROGN + (FOR #:COUNT520 :FROM 1) + (SUM (* 2 X) :INTO #:TOTAL521) + (FOR FOO = (/ #:TOTAL521 #:COUNT520))) + +[mxi]: https://github.com/sjl/cl-losh/blob/55de0419a9b97a35943ce4884b598dbd99cc5670/losh.lisp#L1105-L1151 + + {% endblock article %} diff -r db148e22d042 -r 500ef047ae2c media/css/sjl.less --- a/media/css/sjl.less Fri Aug 19 13:09:32 2016 +0000 +++ b/media/css/sjl.less Tue Sep 20 14:00:45 2016 +0000 @@ -212,11 +212,7 @@ @w-listing-col-padding: 50px; li { - float: left; - width: @w-listing-item - (@w-listing-col-padding / 2); - height: 150px; - margin: 0; - text-align: center; + margin: 0 0 25px 0; a { font: normal 23px/32px @font-fancy; // 3 @@ -239,13 +235,6 @@ .amp { font-style: italic; } - - &:nth-child(odd) { - margin-right: @w-listing-col-padding / 2; - } - &:nth-child(even) { - margin-left: @w-listing-col-padding / 2; - } } } } diff -r db148e22d042 -r 500ef047ae2c media/images/blog/2016/09/loop-macro.jpg Binary file media/images/blog/2016/09/loop-macro.jpg has changed