Threading expressions
Let's say we've got a list of strings from the database:
'("marion" "jake" "paula" "jani" "natasha")
(Note the symbol '
in front of the list. As mentioned before, we need it so that Clojure doesn't confuse the list with a function call. '
is actually a shorthand for function list
.)
We need to process the list:
- Capitalize each string.
- Add a comma string between each string.
- Join all strings into one.
First, capitalizing can be done by applying capitalize
to each element. We know that map
can help with such tasks:
user=> (map capitalize
'("marion" "jake" "paula" "jani" "natasha"))
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: capitalize in this context
Oh, snap! capitalize
function isn't found. If you look into the documentation, you'll see that capitalize
is located in clojure.string
namespace. Namespaces are sort of like modules. We'll talk about namespaces later. For now, we only need one thing: a simple way to "import" a namespace into our current program:
user=> (require 'clojure.string)
nil
user=> (map clojure.string/capitalize
'("marion", "jake", "paula", "jani", "natasha"))
("Marion" "Jake" "Paula" "Jani" "Natasha")
Now is a good moment to talk about the user=>
prompt. It indicates the current namespace. By default, you program lives in user
. You can switch to another namespace or set a new one by using ns
:
user=> (ns clojure.string)
nil
clojure.string=>
ns
returned nil
and now the prompt reflects the changed namespace. Once in clojure.string
, we can use capitalize
without a fully qualified name:
clojure.string=> (map capitalize
'("marion" "jake" "paula" "jani" "natasha"))
("Marion" "Jake" "Paula" "Jani" "Natasha")
We've been using lots of functions from clojure.core
without explicitly requiring them or switching namespaces. That's because clojure.core
along with some other namespaces are auto-required by Clojure for convenience.
Alright, let's get back into user
namespace and continue. The 2nd task: add a comma between each string. clojure.core
has a function interpose
, which does exactly that: inserts the given element between elements of the given sequence.
user=> (interpose ", " (map clojure.string/capitalize
'("marion" "jake" "paula" "jani" "natasha")))
("Marion" ", " "Jake" ", " "Paula" ", " "Jani" ", " "Natasha")
And finally, join
, which is another function from clojure.string
:
user=> (clojure.string/join
(interpose ", " (map clojure.string/capitalize
'("marion" "jake" "paula" "jani" "natasha"))))
"Marion, Jake, Paula, Jani, Natasha"
Phew! It's a long line of code, and you need to read it from inside out, which isn't very comfortable at this scale. Clojure provides a helper threading macro ->>
. (It has nothing to do with threads or processes; rather, it refers to threading through multiple things like when sewing fabrics.)
user=> (->> '("marion", "jake", "paula", "jani", "natasha")
(map clojure.string/capitalize)
(interpose ", ")
(clojure.string/join))
"Marion, Jake, Paula, Jani, Natasha"
->>
takes an expression and passes it as the last argument of the next expression. To pass it as the first argument, we'd use the ->
macro instead.