Commit Diff


commit - dcb77f40c88a34805222d1ffa34a2bdecbe51234
commit + 314cbe1e2512a3396b43933c6a782bc987805fb9
blob - /dev/null
blob + f984024561c31c51ecdcfcb2e118987a70b67741 (mode 644)
--- /dev/null
+++ resources/posts/elisp-threading-macros.md
@@ -0,0 +1,112 @@
+Clojure was the first lisp I've learned.  Well, technically speaking,
+my first one was scheme (racket actually), because was the language
+chosen as an introduction to programming in my university.  Back in
+the days, I was way more stupid than I am now (or at least I like to
+think that) and I didn't really liked the language at all.
+
+Fortunately enough, a couple of months later, I don't remember why, I
+started learning clojure.  I found the idea of code-as-data, the sexp
+thing, the syntax was... interesting.
+
+(don't hate on me: recently I given scheme a second chance and I heavily respect it.  It's really a clean, nice and elegant language)
+
+Fast forward several years: (as a complete elisp noob) I'm hacking a
+piece of my Emacs configuration.  I have a line like this:
+
+```elisp
+(member (nth 2 (org-heading-components)) org-todo-keywords-1)
+```
+
+(now that I think of it, this is not a great example to show of how
+cool is the `->` macro, but still...)
+
+In a hypothetical elisp-clojure mix that line can be written as:
+```clojure
+(->> (org-heading-components)
+  (nth 2)
+  (-> (member org-todo-keywords-1)))
+```
+
+Sometimes some lispy form tends to be written *backwards* due to the
+syntax of the language.  The threading macro lets you *invert* the
+flow of the expression, and sometimes this makes the whole more
+readable.
+
+If you don't know what the `->` macro in clojure does, it does this:
+```clojure
+(-> x
+  (* 5)
+  (/ 2)
+  inc)
+
+;; gets rewritten as
+
+(inc (/ (* x 5) 2))
+```
+
+That is, the first parameter is passed as first argument on the next
+form, and so on.  The `->>` is similar, but adds the parameter as
+*last* argument.
+
+If you haven't seen anything about how macros are written, well, I
+don't think I'm the most qualified to show how they works, but the
+idea is that a macro gets called at compile time, when the compiler
+has just finished reading the whole expression.  The macro is
+therefore a function executed at compile time that returns code.
+
+(these kind of macros are not the only possible.  Some lisp dialects
+allows *reader macro*s, but that's another story)
+
+What follows is an amateurish implementation of the macro in elisp.
+It is definitely not something hard to write (quite the opposite in
+fact) but can be useful as some sort of introduction to macros in
+elisp, to show how *malleable* lisps are and to highlight how clean
+the code is thanks to a little recursion.
+
+```elisp
+(defmacro -> (x &rest xs)
+  "Should work like clojure `->'."
+  (declare (indent defun))
+  (cond
+   ((eq xs nil)
+    x)
+   
+   ((symbolp (car xs))
+    `(-> (,(car xs) ,x)
+       ,@(cdr xs)))
+
+   (t
+    `(-> (,(caar xs) ,x ,@(cdar xs))
+       ,@(cdr xs)))))
+```
+
+First we have our base case: if `xs` is nil (i.e. the case of `(->
+x)`) we can rewrite it as `x`.  Easy, right?
+
+Then we have the two recursive cases:
+
+ - `(-> x fn ...)` gets rewritten as `(-> (fn x) ...)` and
+ - `(-> x (fn ...) ...)` gets rewritten as `(-> (fn x ...) ...)`.
+
+The revelation writing this macro was that when I first learn of
+`caar`, `cadr`, `cdar`, `caadr`, ... I thought of them being a joke
+while now I can see that they can lead to cleaner code (sort of).
+
+The counterpart, `->>`, is almost identical in its implementation:
+
+```elisp
+(defmacro ->> (x &rest xs)
+  "Should work like clojure' `->>'"
+  (declare (indent defun))
+  (cond
+   ((eq xs nil)
+    x)
+
+   ((symbolp (car xs))
+    `(->> (,(car xs) ,x)
+       ,@(cdr xs)))
+
+   (t
+    `(->> (,@(car xs) ,x)
+       ,@(cdr xs)))))
+```
blob - d166521b1d8d2beaf40f10f94a6831ea27cd94ce
blob + fbc4b453541f3f1b16f477cce05d20a31c6e0d93
--- src/blog/posts.clj
+++ src/blog/posts.clj
@@ -1,3 +1,9 @@
+(add-post! {:title "Threading macros for elisp"
+            :slug  "elisp-threading-macros"
+            :date  "2020/06/04"
+            :tags  #{:elisp}
+            :short "(-> '->> symbol-name (concat \" is so cool\"))"})
+
 (add-post! {:title "Updating the ssh key to ed25519"
             :slug  "upgrading-ssh-key-ed25519"
             :date  "2020/06/03"