Foray Into Clojure, Part 0: Setup, syntax, functions

I started this post AFTER starting to work on a Clojure script, when I realized I didn’t feel comfortable enough with the language and tools to actually start working on the script. I needed to feel more comfortable before starting to work.

In this post I go over how to setup a basic Clojure development environment and I solve all the exercises from the “Learn Clojure” guide, with examples and explanations. If you want to clone my solutions and play around with them yourself, feel free to grab the code itself from my GitHub.

If you want, you can read everything I have to say about Clojure by looking at the Clojure tag.

Table of Contents

Starting from the end - my conclusions and experience learning Clojure so far

Clojure feels hostile. It feels like a language which was built for seniors. If I don’t understand something, it always feels like my fault - the docs are there, the tools are there, I’m just not good enough yet.

Now, I’m not a junior developer, by any means. But it felt like the barrier for entry was unnecessarily… well, not high, just hard. You have to learn so much just to get to learn Clojure.

The best analogy I can give is that if learning, say, basic Python feels like a fun workout in the Park, learning Clojure feels like Pai Mei’s training from Kill Bill.

kill bill GIF

Now, THEORETICALLY, after the training is over, I’m supposed to have some super powers. 🦸

So personally (not in a professional capacity) I will continue learning Clojure. But for professional usage, unless you already have a core team that is fluent in Clojure, or a highly specific need to use it - I would pick other languages to go with to get started with projects. Easier learning curve and easier to hire relevant talent. Take this conclusion with a grain of salt! 🧂 I’m still very new here.

Setting up the tools

This is a fresh OS, so I needed to install stuff from scratch. Pretty easy to follow the documentation once you know what you need, but I’ll post a TL;DR here:

Clojure itself

curl -O https://download.clojure.org/install/linux-install-1.10.3.822.sh
chmod +x linux-install-1.10.3.822.sh
sudo ./linux-install-1.10.3.822.sh

Then test with:

❯ clojure --version
Clojure CLI version 1.10.3.822

Leiningen

For working with Clojure projects. Works with apt! So just sudo apt install leiningen.

Test with:

❯ lein --version
Leiningen 2.9.1 on Java 11.0.10 OpenJDK 64-Bit Server VM

Spacemacs

This one is annoying to “start” with. But it’s the de-facto standard editor for Clojure developers, so I have to try it, I guess. Installation is easy enough:

git clone https://github.com/syl20bnr/spacemacs ~/.emacs.d
emacs

It runs quite a lot of installations etc. now, so go grab yourself a coffee while it does its thing.

coffee break

Opening a project to learn with

What I did:

mkdir learn-clojure
cd learn-clojure
lein new app . --force

This caused considerable frustration and even a tweet:

What I should have done:

lein new app learn-clojure

And then I opened that project in spacemacs. Quick sidebar here about the REPL. REPL stands for Read Evaluate Print Loop and means Reading a line, Evaluating the string into things that makes sense in the language, and Printing the result from the Evaluate step. In that sense, a commandline calculator is a REPL as well - not just programming shells!

With Spacemacs and Clojure, you can REPL the code as you’re writing it, and it seems to be the zeitgeist of developing with Clojure. There is quite a lot of documentation about it but the best I’ve found is Practicalli’s Spacemacs guide.

The TL;DR is open the project in spacemacs, then run sesmen-start by typing , ', and then jump to the REPL with , s a. Everything else you want to find using SPC SPC.

Learning Clojure

This part has “spoilers” (if you can even call them that) since I show the exercises’ answers.

All the answers can also be found on a repository I’ve opened.

Syntax

I started with learning the REPL syntax by following the exercises here. Instead of directly using a REPL, I typed all this out in a file and used , e to evaluate and write as a comment.

  ;; 1. Using the REPL, compute the sum of 7654 and 1234.
  (+ 7654 1234)  ; spacemacs printed "=> 8888"

  ;; 2. Rewrite the following algebraic expression as a Clojure expression:
  ;; ( 7 + 3 * 4 + 5 ) / 10.
  (/ (+ 7 (* 3 4) 5) 10)  ; 12 / 5

The next step was to:

Using REPL documentation functions, find the documentation for the rem and mod functions. Compare the results of the provided expressions based on the documentation.

Can’t really do this inside the code example, so I’ll just write what I did. To open the REPL in Spacemacs: , s a calls cider-switch-to-repl-buffer. This is invaluable advice and will become something you run all the time! Get used to it. , s a.

Then, I loaded the REPL functions using (require '[clojure.repl :refer :all]). Later I realized this happens automatically.

Then, to see the docs for rem and mod, all I did was:

learn-clojure> (doc rem)
-------------------------
clojure.core/rem
([num div])
remainder of dividing numerator by denominator.
nil
learn-clojure> (doc mod)
-------------------------
clojure.core/mod
([num div])
Modulus of num and div. Truncates toward negative infinity.
nil

Then I played around a little more, trying rem and mod myself and looking at their source:

learn-clojure> (source rem)
(defn rem
  "remainder of dividing numerator by denominator."
  {:added "1.0"
   :static true
   :inline (fn [x y] `(. clojure.lang.Numbers (remainder ~x ~y)))}
  [num div]
  (. clojure.lang.Numbers (remainder num div)))
nil
learn-clojure> (source mod)
(defn mod
  "Modulus of num and div. Truncates toward negative infinity."
  {:added "1.0"
   :static true}
  [num div]
  (let [m (rem num div)]
    (if (or (zero? m) (= (pos? num) (pos? div)))
      m
      (+ m div))))
nil

Finally, the tutorial went over the very useful find-doc function, which reminded my of the immensely useful man -k and man -K.

Using find-doc, find the function that prints the stack trace of the most recent REPL exception.

learn-clojure> (find-doc "stack trace")
-------------------------
clojure.stacktrace/e
([])
REPL utility.  Prints a brief stack trace for the root cause of the
most recent exception.

Functions

This one took a WHILE. Follow along with the guide!

;; 1) Define a function greet that takes no arguments and prints "Hello". 
;;    Replace the ___ with the implementation: (defn greet [] _)

I changed the function name from greet to greetq1 to avoid clashes between different questions.

(defn greetq1 [] (println "Hello"))
;; Testing in REPL:
;; learn-clojure> (greetq1)
;; Hello
;; nil
;; 2) Redefine greet using def, first with the fn special form and 
;;    then with the #() reader macro.
;;
;; ;; using fn
;; (def greet __)
(def greetq2a (fn [] (println "Hello")))
;; Testing in REPL:
;; learn-clojure> (greetq2a)
;; Hello
;; nil

;; ;; using #()
;; (def greet __)
(def greetq2b #(println "Hello"))
;; Testing in REPL:
;; learn-clojure> (greetq2b)
;; Hello
;; nil

;; 3) Define a function greeting which:
;; Given no arguments, returns "Hello, World!"
;; Given one argument x, returns "Hello, x!"
;; Given two arguments x and y, returns "x, y!"
;; Hint use the str function to concatenate strings
;; (doc str)

Here’s what (doc str) returns on the REPL:

;; learn-clojure> (doc str)
;; -------------------------
;; clojure.core/str
;; ([] [x] [x & ys])
;; With no args, returns the empty string. With one arg x, returns
;; x.toString().  (str nil) returns the empty string. With more than
;; one arg, returns the concatenation of the str values of the args.

At this point, testing at the REPL was getting tiresome. So I started testing with assert calls, instead. After the fact, I’ve probably learned more from writing the assertions, as well. This is a good reminder to Kent Beck’s advice on learning tests:

When do you write tests for externally produced software? Before the first time you are going to use a new facility in the package.

Kent Beck, “Test Driven Development by Example”

As far as I’m concerned, writing asserts for questions in a tutorial is similar in spirit to Learning Test - I’m learning how to write Clojure (since the asserts are in Clojure), I’m learning how the answer should behave (since the question is externally produced, the answer is as well, even if I haven’t written it yet), and when implementing the answer - I’m learning the answer, and if the assert was written correctly.

So back on track:

(defn greeting-q3
  ([] (greeting-q3 "World"))
  ([x] (greeting-q3 "Hello" x))
  ([x y] (str x ", " y "!")))

(assert (= "Hello, World!" (greeting-q3)))
(assert (= "Hello, Clojure!" (greeting-q3 "Clojure")))
(assert (= "Good morning, Clojure!" (greeting-q3 "Good morning" "Clojure")))

;; 4) Define a function do-nothing which takes a single argument x 
;;    and returns it, unchanged.

(defn do-nothing [x] x)

;; NOTE: added some tests:
(assert (= 4 (do-nothing 4)))
(assert (= "asdf" (do-nothing "asdf")))

;; In Clojure, this is the identity function. By itself, identity 
;; is not very useful, but it is sometimes necessary when working 
;; with higher-order functions.

Following the previous question, I was surprised that (defn do-nothing [x] (x)) doesn’t work. After all, why this: (defn do-nothing [x] x), but not this: (defn do-nothing [x] (x))

What I didn’t realize was that I thought that (x) is an expression that would just evaluate to x, but it’s not! (f x) is a list sintactically, and a function invokation semantically. The first position in the list is the thing to invoke (in the function position). So (f) means INVOCATION of f’s VALUE (which normally is a function). (x) means INVOCATION of x’s VALUE, but x’s value is non-invokable!

;; 5) Define a function always-thing which takes any number of arguments, 
;;    ignores all of them, and returns the number 100.

(defn always-thing [& ignoring-these-args] 100)
;; NOTE: How this looks in the REPL:
;; learn-clojure> (always-thing "asdf")
;; 100
;; NOTE: added some tests:
(assert (= 100 (always-thing)))
(assert (= 100 (always-thing 100)))
(assert (= 100 (always-thing "asdf" "zxcv")))

;; 6) Define a function make-thingy which takes a single argument x. 
;;    It should return another function, which takes any number 
;;    of arguments and always returns x.
(defn make-thingy [x] (fn [& args] x))

;; Tests
(let [n (rand-int Integer/MAX_VALUE)
      f (make-thingy n)]
  (assert (= n (f)))
  (assert (= n (f 123)))
  (assert (= n (apply f 123 (range)))))

;; In Clojure, this is the constantly function.

I didn’t understand why the constantly function was useful, so I looked it up here.

;; 7) Define a function triplicate which takes another 
;;    function and calls it three times, without any arguments.

(defn triplicate [f] (f) (f) (f))

Wrote this function to test:

(defn test-triplicate [] (println "Called from triplicate"))

In the REPL, it looked like this:

learn-clojure> (triplicate test-triplicate)
Called from triplicate
Called from triplicate
Called from triplicate
nil

Moving on!

;; 8) Define a function opposite which takes a single argument f. It should 
;; return another function which takes any number of arguments, applies f 
;; on them, and then calls not on the result. The not function in Clojure 
;; does logical negation.
(defn opposite [f]
  (fn [& args] (not (apply f args))))
;; NOTE: For testing, defined these functions:
(defn always-true [& args] true)
(defn sequential-numbers? [x y] (= x (- y 1)))
;; NOTE: Tests:
(assert (= (always-true) true) "always-true does what it says")
(assert (= (always-true (rand)) true) "even when params are passed")
(assert (= ((opposite always-true)) false) "opposite flips the result")

(assert (= (sequential-numbers? 7 8) true) "s-n? works")
(assert (= (sequential-numbers? 7 7) false) "s-n? works")
(assert (= ((opposite sequential-numbers?) 7 8) false) "opposite flips the result")

For testing opposite, I’ve defined these functions:

(defn always-true [& args] true)

Testing in REPL:

learn-clojure> (always-true 1 2 3)
true
learn-clojure> (opposite always-true)
#function[learn-clojure/opposite/fn--7457]
learn-clojure> ((opposite always-true))
false
;; 9) Define a function triplicate2 which takes another function and any 
;; number of arguments, then calls that function three times on those 
;; arguments. Re-use the function you defined in the earlier triplicate 
;; exercise.

(defn triplicate2-with-hashtag [f & args]
  (triplicate #(apply f args)))

Again, defined this function for testing:

(defn test-triplicate2 [x y & zs] (println "x: " x " | y: " y " | zs: " zs))
(defn triplicate2-with-fn [f & args]
  (triplicate (fn [] (apply f args))))

And here’s how the testing looked in the REPL:

learn-clojure> (test-triplicate2 "we've had one, yes" "but what about second breakfast?" "What about elevenses?" "Luncheon?" "Afternoon tea?" "Dinner?" "Supper?")
x:  we've had one, yes  | y:  but what about second breakfast?  | zs:  (What about elevenses? Luncheon? Afternoon tea? Dinner? Supper?)
nil
learn-clojure> (triplicate2-with-hashtag test-triplicate2 "xxx" "yyy" "z1" "z2")
x:  xxx  | y:  yyy  | zs:  (z1 z2)
x:  xxx  | y:  yyy  | zs:  (z1 z2)
x:  xxx  | y:  yyy  | zs:  (z1 z2)
nil
learn-clojure> (triplicate2-with-hashtag test-triplicate2 "xxx" "yyy")
x:  xxx  | y:  yyy  | zs:  nil
x:  xxx  | y:  yyy  | zs:  nil
x:  xxx  | y:  yyy  | zs:  nil
nil

So, I wanted to test this with asserts instead. To do this, I wanted to define a counter. This led me down a rabbit hole of trying to use dynamic scope with the ^:dynamic metadata just by following StackOverflow answers, but that didn’t work. With some advice, I tried atoms, instead. To get started with atoms I ran (find-doc "atom") and I found three seemingly useful things:

  • atom
  • deref (reader macro @)
  • reset! (I should check swap as well at some point)

And with these, I could set up a test!

(def i-start-as-two (atom 2))
(def i-start-as-sixty-four (atom 64))
(defn test-triplicate3 [x] (reset! x (* @x 2)))
(def expected-starting-at-2 16)
(assert (=
         (triplicate2-with-fn test-triplicate3 i-start-as-two)
         expected-starting-at-2)
        (str "expected " expected-starting-at-2 ", got " @i-start-as-two))
(def expected-starting-at-64 512)
(assert (=
         (triplicate2-with-fn test-triplicate3 i-start-as-sixty-four)
         expected-starting-at-64)
        (str "expected " expected-starting-at-64 ", got " @i-start-as-sixty-four))

The following exercises deal with Java interop, which I find exciting. I’s a really cool feature of the language in my opinion. A lot of great stuff is written in Java.

java

;; 10) Using the java.lang.Math class (Math/pow, Math/cos, Math/sin, Math/PI), 
;; demonstrate the following mathematical facts:

;; 10.1) The cosine of pi is -1

While true, this exercise is written in a somewhat misleading fashion! Check out these asserts to understand why:

(assert (not (= (Math/cos Math/PI) -1)))
(assert (= (Math/cos Math/PI) -1.0))
(assert (not (= (type -1) (type -1.0))))

And from the REPL:

;; learn-clojure> (type (Math/cos Math/PI))
;; java.lang.Double
;; learn-clojure> (type -1)
;; java.lang.Long
;; 10.2) For some x, sin(x)^2 + cos(x)^2 = 1
(defn pythagorean-identity
  "The Pythagorean identity function (LHS). ⊿

  See https://en.wikipedia.org/wiki/Pythagorean_trigonometric_identity.

  It's actually implemented, even though mathematically it's already 
  proven, so it can be implemented by simply returning 1..."
  [x] (+ (Math/pow (Math/sin x) 2) (Math/pow (Math/cos x) 2)))

;; Asserting that the pythagorean identity equals 1 for a random 
;; number. We can test ALL numbers, but I don't have that
;; kind of time 🐢
(assert not (= (pythagorean-identity (rand)) 1))
(assert (= (int (Math/round (pythagorean-identity (rand)))) 1))

;; 11) Define a function that takes an HTTP URL as a string, 
;;     fetches that URL from the web, and returns the content as a string.
;;     Hint: Using the java.net.URL class and its openStream method. 
;;     Then use the Clojure slurp function to get the content as a string.

(defn http-get [url]
(slurp (.openStream (java.net.URL. url))))

(assert (.contains 
  (http-get "https://wtfismyip.com/json") "YourFuckingIPAddress"))

;; 11) [cont.] In fact, the Clojure slurp function interprets its argument
;;     as a URL first before trying it as a file name. Write a 
;;     simplified http-get:

(defn http-get-simple [url]
(slurp url))

(assert (.contains 
  (http-get-simple "https://wtfismyip.com/json") "YourFuckingIPAddress"))

;; 12) Define a function one-less-arg that takes two arguments:
;; * f, a function
;; * x, a value
;; and returns another function which calls f on x plus any additional arguments.

(defn one-less-arg [f x]
(fn [& args] (apply f x args)))
;; In Clojure, the partial function is a more general version of this.

;; NOTE: Now, to test:
(assert (= "firstargSomeMoreArgs" ((one-less-arg str "firstarg") "Some" "More" "Args")))

;; 13) Define a function two-fns which takes two functions as arguments, f and g. It returns another function which takes one argument, calls g on it, then calls f on the result, and returns that.
;; That is, your function returns the composition of f and g.

(defn two-fns [f g]
  (fn [x] (f (g x))))

;; NOTE: now, to test:
(def composed-sin-and-arcsin (two-fns (fn [x] (Math/sin x)) (fn [x] (Math/asin x))))
(def random-value (rand))
(assert (= (composed-sin-and-arcsin random-value) random-value))

phew

I had some real roadblocks, but I felt like I was getting faster towards the end.

What’s next?

So my plan is to continue with the Learn Clojure tutorial, and I’m assuming it will go a lot faster. So expect the next parts to come up on the blog at some point.


clojure

2913 Words

2021-05-27 01:03 +0300