Hitchhiker's Guide to Clojure

Don't Worry About the Parens

Hitchhiker’s Guide to Clojure

The following is a cautionary example of the unpredictable combination of Clojure, a marathon viewing of the BBC’s series “The Hitchhiker’s Guide to the Galaxy”, and a questionable amount of cheese.

There have been many tourism guides to the Clojure programming language. Some that easily come to mind for their intellectual erudition and prose are “The Joy of Touring Clojure”, “Touring Clojure”, “Clojure Touring”, and the newest edition of “Touring Clojure Touring”. However, none has surpassed the wild popularity of “The Hitchhiker’s Guide to Clojure”. It has sold over 500 million copies and has been on the “BigInt’s Board of Programming Language Tourism” for the past 15 years. While, arguably, it lacked the in-depth coverage of the other guides, it made up for it in useful practical tips, such as what to do if you find a nil in your pistachio. Most of all, the cover had the following words printed in very large letters: Don’t Worry About the Parens.

To tell the story of the book, it is best to tell the story of two people whose lives were affected by it: Amy Denn, one of the last remaining Pascal developers in Cincinnati, and Frank Pecan, a time traveler, guidebook reseacher, and friend of Amy.

Amy, at this moment, was completely unaware of the chronological advantages of her friend, being preoccupied with the stark fact that she was about to be fired. She had been given a direct order from her CEO to deploy the code at 3:05pm. It was now 3:00pm and she had realized that if she did so, all the data painstaking collected about the effects of Throat Singing on the growth rate of tomatoes would be erased. Unfortunately, the CEO did not really understand or trust anything having to do with technology or programming. In truth, the only two things that he seemed to care about were tomatoes and checklists of unreasonable things. The fact that no course of action available to her in the next 5 minutes would help her employment situation, agitated Amy so much that she was violently shelling and eating pistachio nuts.

The “Hitchhiker’s Guide to Clojure” says that pistachios are Nature’s most perfect s-expression. An s-expression is recursively composed of s-expressions or an atom. In the case of the humble pistachio, the atom is the nut inside. The atom simply evaluates to itself. This is best seen is an example where the following expressions are evaluated in the Clojure REPL

1
2
3
4
"hi" ;;=> "hi"
1 ;;=> 1
true ;;=> true
nil ;;=> nil

`

Which leads to the very practical tip of what to do if you find a nil in your pistachio. The answer, of course, is to be thankful that you have a value that represents the absence of a value – and to get another pistachio.

In Clojure, a s-expression is written with parens. The first element within the parens is an operator or function and the rest of the elements are treated as data, some of which can be s-expression themselves.

1
2
(+ 1 2) ;;=> 3
(+ 1 (+ 2 2)) ;;=> 5

Considering the pistachio again, we can think of the nut in the shell as an s-expression, (providing we also imagine an operator or function right in front of the nut).

Here we define a function that will turn the nut red, by appending the string “red” to the nut-name.

1
2
3
4
(defn red [nut]
  (str "red " nut))

(red "nut1") ;;=> "red nut1"

Notice that if we put a quote in front of the expression, it will no longer be evaluated.

1
'(red "nut1") ;;=> (red "nut1")

quoting the expression turns it into a list, which we can then manipulate with other s-expressions (code as data).

1
2
3
(first '(red "nut1")) ;;=> red

(last '(red "nut1")) ;;=> "nut1"

If we try to evaluate the s-expression with just the nut name in the parens, we get an error because there is no function in the first slot.

1
2
("nut1")
;;=> ClassCastException java.lang.String cannot be cast to clojure.lang.IFn

The whole thing of having to have a function in front of the nut in the pistachio has invited much heated debate on the suitability of pistachios being held up as the paragon of an s-expression. But critics have failed to explain the corroborating evidence of red pistachio nuts, or find a more suitable nut.

Amy’s time traveling friend, Frank, is due to appear on the scene momentarily to reveal that the whole world is really made of Clojure Datomic datoms. Furthermore, a transaction is going to be evaluated soon, which will retract all the facts on EVERYTHING. The practical effect of this will be that nothing will have an attributes. A world without any attributes at all would be quite boring and, for all purposes, be non-existent. Luckily for Amy, Frank is a Datomic Time Traveller and has a hand-held “evaluator” which will save them. Also luckily, the readers will be spared dialog, since the author can never figure out where to put the punctuation and is really rubbish at it. Only one phrase will be illustrated. This is the rather important one, having been uttered by Amy after it was explained to her that she, and the entire world around her, was entirely composed of Clojure:

“Isn’t that the language with a lot of parens?”

To which, Frank handed her the “Hitchhiker’s Guide to Clojure” and pointed to the words on the front cover, “Don’t Worry About the Parens.”, and turned to the first page.

“There is absolutely no need to worry about the parens. It is known today that the first really important discovery of humankind was not fire, but Paredit. Paredit mode magically acts to insert and balance the right parens to the point where they actually can no longer be seen. This is evident by just looking around you. The world is made of Clojure and there are millions, billions, and trillions of parens all around you and your tea cup right now. Yet, you don’t see them. Paredit mode.”

At the urging of Frank, Amy quickly stuffed the remaining pistachios in her pockets while he readied his evaluator. The display showed some large integer value, that decreased as he pushed the buttons on the console. Finally, he pushed the large red button and two parens started glowing on either side of them … and they disappeared.

Hitchhiker’s Guide to Clojure - Part 2

Amy and Frank were hurtled quite rapidly through time and space after attaching themselves to a transaction headed through the Datomic Transactor. From there things slowed down a bit, then took a sharp left and ricocheted off again with incredible speed until they landed in another Datomic Peer, and finally appeared in the same room. Amy was quite startled by the anti-climatic nature of the whole dematerializing and rematerializing in the same exact spot, and didn’t really know what to do next. She surveyed her office and found it exactly the same, except for two distinct details. For one, the pistachio shells had disappeared, and for another, the date on the computer showed yesterday at 8:00 am. She tried to connect these facts rationally with the pistachios in her pocket and finally said,

“I am about to come into work.”

Frank, who was busily hunting through his blue zippered pack around his waist, looked up briefly.

“Well, we better get out of here then, I only have a blue fanny pack.”

The Hitchhiker’s Guide to Clojure explains that the “fanny pack”, or “bum bag”, is the symbol of a licensed Chrono-agent. The rank of the Chrono-agent can be clearly determined by its color on the ROYGBIV scale.

The origins of this licensing method can be traced to an embarrassing incident in human history known as “The Great Flood”. A junior Chrono-agent was trying to increase the yield of a tomato crop during a dry spell and was trying to do the following recursive function in his evaluator:

1
2
3
4
5
6
7
8
9
10
11
12
(defn rain [days]
  (when (pos? days)
    (println (str "Rain: " days))
    (rain (dec days))))

(rain 5)
;;Output
;;  Rain: 5
;;  Rain: 4
;;  Rain: 3
;;  Rain: 2
;;  Rain: 1

Unfortunately, he made the rookie mistake of forgetting to decrement the days before passing it to the recursive function.

1
(dec 5) ;=> 4

The result of which was severely overwatered tomatoes.

1
2
3
4
5
6
7
8
9
10
11
12
(defn rain [days]
  (when (pos? days)
    (println (str "Rain: " days))
    (rain days)))

(rain 5)
;;  Rain: 5
;;  Rain: 5
;;  Rain: 5
;;  Rain: 5
;;  Rain: 5
;;  ...(you get the idea)

It is interesting to note that he could he written the same function with a recur instead.

1
2
3
4
5
6
7
8
9
10
11
12
(defn rain [days]
  (when (pos? days)
    (println (str "Rain: " days))
    (recur days)))

(rain 5)
;;Output
;;  Rain: 5
;;  Rain: 5
;;  Rain: 5
;;  Rain: 5
;;  Rain: 5

That would have had the nice effect of not consuming the stack, (which is fabulous for constructing those lovely fibonacci sea shells for beach vacations), but without decrementing the parameter in the recursive call, it wouldn’t have really helped.

A senior Chrono-agent was dispatched to sort out the mess. By the time he got there and stopped the rain, there was not much left to work with. Thankfully, he was quite versed in lazy and infinite aspects of Clojure. For instance, take this vector of 2 chickens:

1
[:hen :rooster]

It can be transformed into an infinite lazy list of chickens by using cycle.

1
(cycle [:hen :rooster])

What really set the senior Chrono-agent apart from his junior colleague, was that he did not put the infinite sequence in the evaluator. If he had, there would have been another embarrassing incident in human history, this time involving an over-abundance of poultry. Instead, he used take to get the first n infinite chickens.

1
2
3
4
5
(take 5 (cycle [:hen :rooster]))
;;=> (:hen :rooster :hen :rooster :hen)
(take 10 (cycle [:hen :rooster]))
;;=> (:hen :rooster :hen :rooster :hen :rooster :hen :rooster :hen
:rooster)

After that, the council of Chrono-agents, decided to license evaluator use. Low-level recursion requires the 2nd highest indigo level rank. The highest violet rank, of course, belonging only to the Macro Masters. All lower levels are required to stick to the safer, higher level abstractions like for, map, or reduce.

Amy was still watching Frank busily rumaging through his pack in the office . Finally he found what he was looking for, his hand emerging triumphantly with a fistful of mini-marshmallows.

“Got it. Come on, let’s go! Someone is trying to destroy the world and we need to see the otters.”

Hitchhiker’s Guide to Clojure - Part 3

Amy and Frank fled down the stairs from her office and met an unexpected obstacle to their exit, a locked door. As they peered out the window, they saw yesterday’s Amy pull up in the parking space, get out, retrieve her laptop, and start to head in the front door.

“Oh good, we can take your car”, said Frank.

Amy took a second to recover from the shock of seeing what her hair really looked like from behind and then asked, “But, how can we get to it? The door is locked, and we can’t go back up to the office… I would meet myself.”

Frank smiled, pulled out the Hitchhiker’s Guide to Clojure and pulled up a page with the heading Locked Doors and Other Small Bothers.

One of the reasons for the surprising success of The Hitchhiker’s Guide to Clojure is helpful advice of on an assortment of practical matters.

Locked doors are a common nuisance in modern times. Fortunately, Clojure provides a very handy function for such occasions, fnil. This commonly overlooked function, takes an existing function and returns a new function that allows you to specify a default for a nil parameter. For example, take this locked door:

1
2
3
4
5
(defn locked-door [key]
        (if key "open" "nope - staying shut"))

(locked-door :key) ;=> "open"
(locked-door nil) ;=> "nope - staying shut"

In this case, the simple application of fnil will help remove this pesky obstacle.

1
2
3
4
(def this-door (fnil locked-door :another-key-that-works))

(this-door :key) ;=> "open"
(this-door nil) ;=> open

Please be advised, that some doors are locked for a good reason. It is left to the user’s discretion. But it is highly recommended in Norway’s moose regions, to think twice.

They unlocked the door and headed for Amy’s car. She couldn’t decide whether she was surprised or not to find her keys in her pocket, so she gave up and just got in instead. After a short drive, they arrived at the zoo and navigated their way through various school groups and arrive at the Aquarium.

Amy at this point, having prided herself on her adaptable nature, was still having trouble processing the latest events. She had discovered that Frank was a Datomic time traveller, the world was made of Clojure, and it was also about to be destroyed in a short future that she just came from. Her rational brain, (which was currently working way too hard), was quite relieved to be distracted by the sight of two really adorable otters. They were floating contentedly around the pool, occasionally stopping to crack an Abalone shell on their fuzzy tummies.

Her rational brain, after having a nice breather, finally re-asserted itself and made Amy ask Frank:

“Why are we here?”

“Otters are the front-line Chrono-guards, of course.”

He went on to explain that otters are tasked with the important job of keeping a close watch on human civilization and making critical, minor adjustments to keep things on an even track. All those nature videos of otters cracking shells with rocks? They are really evaluating Clojure expressions crucial to our way of life. Most of the time, they prefer to do their work remote. They find floating on their backs in the peaceful waters the most productive work environment. However, sometimes they will construct zoos or aquariums, when their work requires them to keep a closer watch on some areas. In times of great need, they might even take a human form for a short while. Recently, one of their agents was inadvertently exposed and required a few extra Abalone shells to straighten out.

Frank opened his pack and handed his evaluator to Amy to hold while he fished out four mini-marshmallows. He gave two to Amy and then proceeded to put one in his ear and the other in his mouth. More remarkably still, he appeared to be speaking with the otters.

Mini-marshmallows are the best way to create portable Clojure core.async channels that won’t melt in your hands.

To construct a channel simply use chan

1
(def talk-to-otters-chan (chan))

Channels by default are unbuffered, which keeps them at the mini-marshmallow size. It requires a rendezvous of a channel producer and consumer to communicate. In the case of otters, someone to talk to the otters and the otters, themselves, to listen. Be advised that with a regular blocking put >!!, the main thread will be blocked. That is, if you try to speak to the otter, you will be stuck there until it gets around to listening. This isn’t the best case for the talker if the otter was busy, so one approach would be to use a future to talk to the otter with a blocking put >!!.

1
2
(future (>!! talk-to-otters-chan "Hello otters.")) ;=>#<Future@3c371c41: :pending>
(<!! talk-to-otters-chan) ;=> "Hello otters."

One could also use a buffered channel, but that increases the size of the marshmallow.

1
2
3
4
5
6
(def talk-to-otters-chan (chan 10)) ;;create channel with buffer size 10
(>!! talk-to-otters-chan "Hello otters.") ;=> nil
(>!! talk-to-otters-chan "Do you know anything about the world ending?") ;=> nil

(<!! talk-to-otters-chan) ;=> "Hello otters."
(<!! talk-to-otters-chan) ;=> "Do you know anything about the world ending?"

The best way to conserve space and time is to use asynchronous communication with go blocks that wont’ block the threads. Inside these go blocks one can use regular non-blocking puts >! and gets <!.

1
2
3
4
5
6
7
8
9
10
11
(def talk-to-otters-chan (chan))
(go (while true
      (println (<! talk-to-otters-chan))))
(>!! talk-to-otters-chan "Hello otters")
(>!! talk-to-otters-chan "Do you know anything about the world ending?")
(>!! talk-to-otters-chan "Also, you are really fuzzy and cute.")

;; (This prints out in the REPL as you talk to the otters)
Hello otters
Do you know anything about the world ending?
Also, you are really fuzzy and cute.

This compact, lightweight, and asynchronous method of communication is well suited to conversations and messaging of all sorts, including conversing with human, animals, and other Clojure-based life forms.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(def talk-chan (chan))
(def listen-chan (chan))
(go (while true
      (println (<! listen-chan))))
(go (while true
      (>! listen-chan
          (str "You said: "(<! talk-chan)
                " " "Do you have any Abalone?" ))))
(>!! talk-chan "Hello otters")
(>!! talk-chan "Do you know anything about the world ending?")
(>!! talk-chan "Also, you are really fuzzy and cute.")

;; (This prints out in the REPL as you talk to the otters)
You said: Hello otters Do you have any Abalone?
You said: Do you know anything about the world ending? Do you have any Abalone?
You said: Also, you are really fuzzy and cute. Do you have any Abalone?

Amy put one of the mini-marshmallows in her ear. She immediately began to hear the conversation that Frank was having with the otters.

“But who would want to destroy the entire world? That is really kinda over-board.”

“I don’t really know, but there was someone on Galactic Hacker News the other day that was quite tiffed over the idea that Clojure was considered a Lisp.”

Amy reached to put the other marshmallow in her mouth to ask a very important question. But unfortunately, as she moved her hand, she accidentally pushed the big red Source button on the evaluator. Suddenly, she and Frank were swept up in a vortex that spun them around and sucked them down into the ground.