Hy and anaphoric macros

There’s a nice contrib module in Hy called anaphoric, that (surprise, surprise) contains bunch of anaphoric macros. Wikipedia has nice definition for these in general:

An anaphoric macro is a type of programming macro that deliberately captures some form supplied to the macro which may be referred to by an anaphor (an expression referring to another). Anaphoric macros first appeared in Paul Graham‘s On Lisp and their name is a reference to linguistic anaphora—the use of words as a substitute for preceding words.

Hy documentation lists several of them: ap-if, ap-each, ap-each-while, ap-map, ap-map-when, ap-filter, ap-reject, ap-dotimes, ap-first, ap-last and ap-reduce. I’m not going through all of them in detail, but instead picking couple of them to highlight what the concept is about (I use them all the time while coding).

First step is to require the macros so we can use them:

(require hy.contrib.anaphoric)

Lets start with ap-map. It works in similar way to map, but instead of using function to map over a collection, a form is used and special name it, which refers to current element. Following two examples produce the same result. First one uses map with lambda function, while latter one uses ap-map.

=> (list (map (fn [it] (* 2 it)) (range 10)))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

=> (list (ap-map (* 2 it) (range 10)))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

So if transformation is rather short and you don’t want to use lambda or even named function, ap-map is a good alternative.

Lets look more close the resulting code (results are prettified for sake of readability):

=> (macroexpand '(ap-map (* 2 it) (range 10)))
((fn []
  (setv :f_1238 (lambda [it] (* 2 it)))
  (for [:v_1237 (range 10)]
    (yield (:f_1238 :v_1237)))))

Interesting in this is how :f_1238 and :v_1237 names are generated by gensym in order to avoid collisions with any other names in code. Basically code is just a for loop which yields results of applying lambda function for each element in the sequence.

Note that there’s parenthesis around the function definition form. This causes the newly constructed function be called immediately.

Lets contrast this with ap-each, which is a macro that exists solely for purpose of creating side effects.

=> (ap-each [1 2 3 4 5] (print it))
1
2
3
4
5

Expanding that will result following code (again prettified for readability):

=> (macroexpand '(ap-each [1 2 3 4 5] (print it)))
(for* [it [1 2 3 4 5]]
  (do (print it)))

Just a bog standard for loop with iterator variable named as it. Here I actually learned something new: body of the ap-each can consists of multiple forms, as they are all packed inside do in the resulting code. So following will work too:

=> (ap-each [1 2 3 4 5] (print "1: " it)
...                     (print "2: " (* 2 it)))
1:  1
2:  2
1:  2
2:  4
1:  3
2:  6
1:  4
2:  8
1:  5
2:  10

Macros that are useful for handling sequences are: ap-each, ap-each-while, ap-map, ap-map-when, ap-filter, ap-reject, ap-first, ap-last and ap-reduce. Most of them produce a new sequence, exceptions being: ap-each, ap-each-while, ap-first, ap-last and ap-reduce. ap-each and ap-each-while are useful for producing side effects, while ap-first, ap-last and ap-reduce produce as single element.

For example, one way of finding how many times each element is present in a sequence is following:

=> (ap-reduce (do (if (in it acc)
...                 (assoc acc it (inc (get acc it)))
...                 (assoc acc it 1))
...                acc)
...            [2 4 3 6 5 4 3 2 7 8 5 5 7 2 4 7 9 0]
...            {})
{0: 1, 2: 3, 3: 2, 4: 3, 5: 3, 6: 1, 7: 3, 8: 1, 9: 1}

But why do we need to have do block and acc at the end of it? That’s because form in ap-reduce should return new value of acc and assoc doesn’t do that. If we wouldn’t have explicit return there, acc would be nil after first iteration and code would fail (actually happened to me before I caught the error).

Let’s look more closely how the macro expands (I prettified the result, for sake of readability):

=> (macroexpand '(ap-reduce (do (if (in it acc)
...                               (assoc acc it (inc (get acc it)))
...                               (assoc acc it 1))
...                              acc)
...                          [2 4 3 6 5 4 3 2 7 8 5 5 7 2 4 7 9 0]
...                          {}))
((fn []
   (setv acc {})
   (ap-each [2 4 3 6 5 4 3 2 7 8 5 5 7 2 4 7 9 0]
            (setv acc (do (if (in it acc) 
                            (assoc acc it (inc (get acc it))) 
                            (assoc acc it 1))
                          acc)))
            acc))

It’s our friend ap-each again. Macro constructs an ananymous function with local variable acc and uses ap-each to mutate that. The final step is to return the value of acc caller.

So anaphoric macros help you to write more concise code and focus on actual algorithm instead of getting lost on temporary variables and let forms. I find them as a useful addition to my toolbelt of parentheses.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s