Tom Purl's Blog

clojure

I was working my way through the Elyses Destructured Enchantments exercise in the Clojure track of the excellent Exercism training site and it really expanded my knowledge of sequential destructuring It’s very cool, understanding the basics of that makes it much easier pass around sequences and understand other people’s code.

One side effect of working on this exercise was learning about combining sequences (i.e vectors, lists, and ordered sets). There are tons and tons of functions available in the standard library that do something like this and I had quite a bit of difficulty finding one that worked for this scenario.

All I had to do was combine one or more numbers with a vector of strings. Should be easy right? Well, here’s what I tried:

user> (def strings ["one" "two" "three"])
;; => #'user/strings
user> (concat 9 strings)
Error printing return value (IllegalArgumentException) at clojure.lang.RT/seqFrom (RT.java:553).
Don't know how to create ISeq from: java.lang.Long
user> (concat [9 strings])
;; => (9 ["one" "two" "three"])
user> [9 strings]
;; => [9 ["one" "two" "three"]]
user> (conj 9 strings)
Execution error (ClassCastException) at user/eval13467 (form-init5656966070595896937.clj:47).
class java.lang.Long cannot be cast to class clojure.lang.IPersistentCollection (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IPersistentCollection is in unnamed module of loader 'app')
user> (conj [9 strings])
;; => [9 ["one" "two" "three"]]

Now take those lines, multiply it by 20 and add 5 or 6 other functions that seem to be able to do what I want and you’ll begin to appreciate my frustration 😠 Some functions kindof worked (like into) but they didn’t give me the ability control where I placed the number.

And of course, the most frustrating part is that this is supposed to be easy. I’m sure I could write something requiring 3 or 4 functions to do this but manipulating lists is Clojure’s bread and butter – there was certainly a very simple, idiomatic solution that I was missing.

In the end I finally broke down and started Googling for other people’s solutions and found the “magic function”: flatten:

(flatten [9 strings])
;; => (9 "one" "two" "three")

Oh well, I’m glad I finally found this, and I’m assuming that I’ll be using it a lot in the future.

Tags: #clojure, #exercism

Clojure has a few fun macros that help your thread an expression through one or more forms. These macros are also sometimes referred to as the thrush operators. Here’s a few:

(There are more of these, but this is a good place to start. And damn are they hard to find using a search engine. Save yourself some grief and just bookmark ClojureDocs.)

Here’s basically what I think they do. In functional programming you tend to think “inside-out”. That is, you start manipulating your data in the middle of your code and then send those results to one or more wrapping functions.

So for example, let’s say you wanted to know how much to tip for a check, and you prefer to calculate the amount based on the post-tax total. The conventional way to do that with a functional language is like this:

user> (def amount 20.00)
;; => #'user/amount
user> (def sales-tax 0.075)
;; => #'user/sales-tax
user> (def tip-percetage 0.2)
;; => #'user/tip-percentage
user> (* (+ (* amount sales-tax) amount) tip-percentage)
;; => 4.3

To read that code you would first look at the innermost expression, (* amount sales-tax). You would then take the result of that operation (a sequence of even numbers) and pass that to the + function.

This is a bit unfamiliar to people who are used to shell scripting, which depend upon pipelines. Here’s an example:

ps auxwww | grep -v grep | grep firefox
#> tom         5572  4.0  9.3 6142372 726572 ?      Sl   Dec28 179:09 /usr/lib/firefox/firefox

Here, the output of the first command, ps auxwww is passed to the first grep command, which filters out any line containing the string “grep” using the -v flag. Then that output is sent to the second grep command, which filters out everything except the lines containing the string firefox.

You could argue that the shell pipeline is a bit easier to read, because it goes left-to-right. There isn’t really any nesting. And that’s what I believe the thrush operators are for – making it easier to specify how you want to manipulate your data in a pipeline.

So here’s the Clojure example again, this time with a thrush operator:

user> (-> amount
    (* sales-tax)
    (+ amount)
    (* tip-percentage))
;; => 4.3

This evaluates to this:

user> (use 'clojure.walk')
;; => nil
user> (macroexpand-all '(-> amount (* sales-tax) (+ amount) (* tip-percentage)))
;; => (* (+ (* amount sales-tax) amount) tip-percentage)

…which is exactly what we had above. Just easier to read. Also, how cool is the macroexpand-all function? I’m so happy to have learned about this today.

The difference between ->> and -> was a bit confusing to me at first, but it ll has to do with where the expr is placed in the following forms. Here’s an example:

user> (def c 5)
;; => #'user/c
user> (-> c (+ 3) (/ 2) (- 1))
;; => 3
user> (->> c (+ 3) (/ 2) (- 1))
;; => 3/4
user> (macroexpand-all '(-> c (+ 3) (/ 2) (- 1)))
;; => (- (/ (+ c 3) 2) 1)
user> (macroexpand-all '(->> c (+ 3) (/ 2) (- 1)))
;; => (- 1 (/ 2 (+ 3 c)))
  • -> places c as the second item in all forms (i.e. right after the function name)
  • ->> places c as the last item in all forms

Either way, I’m happy to have discovered these fantastic macros. I imagine it will make my Clojure code much simpler to imagine and read.

Tags: #clojure

Coding is my favorite puzzle-solving experience, and the best site I’ve ever found for coding exercises is Exercism. Not only is it free to use, it has an excellent interface, tons of great exercises, and supports dozens of languages. Even Emacs lisp 🐃 Heck, you can even receive (or give) mentoring from volunteers for free. I highly recommend trying it out, no matter what level you are at as a programmer or hobbyist.

I spent quite a bit of time on this site about a year and a half ago. I was very happy to see that the site has only improved during that time. The UX is even better and there are many more exercises.

I decided to try the Clojure exercises again because it’s the most enjoyable language I’ve ever used. It is concise, modern, functional, and integrates wonderfully with Emacs using Cider. Also, it gives me an excuse to re-read my favorite programming book ever.

Anywho, I’m going to try and blog about what I learn from these exercises here. I hope some of the posts will help others learn as much as they will help me.

Tags: #clojure, #exercism, #emacs