Ryan's Manuals

Clojure

~32m skim, 6,780 words, updated Jan 29, 2025

Top 

Enterprise grade magick.


Contents




Programming is tough. Especially at first.

Whatever you are learning or writing, keep in mind that every language was created with a set of principles and use cases in mind - some more than others.

Learn to reject your ego. Learn to love burning code.

Name things what they are. Begin with your program’s final state in mind!



Why Clojure?

Clojure, as a language and an ecosystem, is unique.

Clojure’s technical strength lies in its roots as a LISP and the stability and power of the JVM. The benefits of having an extremely simple syntax (with some well designed caveats) means it is easy to understand both for people for parsers. Clojure, in this aspect, is not unique among LISPs like scheme or common lisp.

Here is the syntax - “All Clojure operations have the same syntax: opening parenthesis, operator, operands, closing parenthesis” 1

;v Operator
(+ 1 2 3 4)
;  ^ ^ ^ ^ Operands
;; =>  10

While this seems like a trivial and strange thing to point out as the introduction to a language, this radically simple syntax brings numerous benefits:

What sets Clojure apart from other LISPs is its community and community-supported tooling and libraries. Where other LISPs frequently succumb to the LISP curse, Clojure has managed to escape and foster a healthy community set amongst an already strong ecosystem of enterprise-grade and battle-tested libraries.

This combination of battle-tested libraries and a functional paradigm lends itself well to designing systems that can scale in a simple and powerful manner.

Additionally, Clojure is exceptionally stable.2 Tools written with Clojure are likely to be supported and stable for a long time due to careful design and lack of rewrites.2 This is evident in the graphs below, which attempt to visualize how often code within the Clojure language is replaced versus another functional JVM language, Scala. As is evident below, Clojure is extremely conservative about introducing breaking changes.

Figure 1: The Clojure language codebase

Figure 1: The Clojure language codebase

Figure 2: The Scala language codebase

Figure 2: The Scala language codebase

These graphs are from A History of Clojure , and I probably can’t explain the strengths and history of the language better than the man himself.

Philosophy

Improving Productivity

Video: How startups can move fast with Clojure (by Bradford Cross)

Startups should focus on:

Rebuilding things from scratch sucks and writing stateless functions that transform data potentially enables better code reuse.

Subverting the Curse of Lisp

Programmers know the benefits of everything and the tradeoffs of nothing

– Rich Hickey

Ah, the Curse of Lisp !!! The arcane magic powering Grammarly , the London Metro , NuBank, and other companies3 allegedly has a dark and hidden secret.

First, if you don’t know: Video: What is the Curse of Lisp?

The curse of LISP is that it is too powerful, promoting hyper customized solutions and individualism. A solo developer now has the power to bend and flex reality to his whims. The drawback is that this causes fewer common tools to emerge - developers can just build them themselves fairly easily, leading to a divergence of methods. Why share when it’s so easy just to write your own stuff?

Figure 3: An old Symbolics keyboard built for editing LISP

Figure 3: An old Symbolics keyboard built for editing LISP

Corporations would not want to enable this - preferring replaceable cogs. This is detailed in the intro to Simply Scheme , and below. The Corporations have a very valid point here that must be stressed: The goal is to reliably get real work done, not to navel gaze.

I’ve heard rumours4 that Clojure subverts this curse from many angles. Through understanding the curse, Clojurists have built a thriving business-friendly developer support community. The ability to leverage Java libraries allows old battle-hardened enterprise libraries to be leveraged from within a lisp program. We’re giving it a go regardless, as I want to work in a LISP for a minute.

Computing Schools of Thought

The the intro to Simply Scheme provides caricatures of the two major schools of thought regarding the teaching of computer science:

1 - The conservative view: Computer programs have become too large and complex to encompass in a human mind. Therefore, the job of computer science education is to teach people how to discipline their work in such a way that 500 mediocre programmers can join together and produce a program that correctly meets its specification.

2 - The radical view: Computer programs have become too large and complex to encompass in a human mind. Therefore, the job of computer science education is to teach people how to expand their minds so that the programs can fit, by learning to think in a vocabulary of larger, more powerful, more flexible ideas than the obvious ones. Each unit of programming thought must have a big payoff in the capabilities of the program.

This is posted here for the reader to ponder without further comment.

Literate Programming

I’ll be using org-babel-clojure to write and run code within this manual directly. Learning, remembering, and teaching now mix. This approach is one of Donald Knuth’s methodologies5

The Literate programming idea has regained popularity these days in the form of data notebooks, and it is certainly to my taste.

If the evaluated result is simple, it’ll have a little arrow ‘=>’ beside it in the css, though not in the text document on disk.

Like so:

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

By integrating ox-hugo I have been able to somewhat improve the default results output and provide scalar value results, which render much nicer than the tables used by Hugo by default. If that didn’t make sense, no worries - I’m just happy that the result below looks like a lisp list rather than a table.

(vals {:a 1 :b 2})
;; =>  (1 2)

Eval & Apply

An idiot admires complexity, a genius admires simplicity, a physicist tries to make it simple, for an idiot anything the more complicated it is the more he will admire it, if you make something so clusterfucked he can’t understand it he’s gonna think you’re a god cause you made it so complicated nobody can understand it. That’s how they write journals in Academics, they try to make it so complicated people think you’re a genius”

– Terry A. Davis, Creator of Temple OS

All LISP interpreters are built to run an eval and apply cycle. Commonly, eval-apply is portrayed in a similar fashion to a yin-yang for its foundational importance and similarity to the concept of creation and destruction. The power of LISP metaprogramming rests upon the simplicity of this process.

;; Evaluate  an expression and determine what to do
(eval '(+ 1 2))  ; => 3

;; Apply functions to arguments and return results
(apply #'+ '(1 2 3))  ; => 6

The metacircular evaluator is essentially a Scheme formulation of the environment model of evaluation described in 3.2. Recall that the model has two basic parts:

1 - To evaluate a combination (a compound expression other than a special form), evaluate the subexpressions and then apply the value of the operator subexpression to the values of the operand subexpressions.

2 - To apply a compound procedure to a set of arguments, evaluate the body of the procedure in a new environment. To construct this environment, extend the environment part of the procedure object by a frame in which the formal parameters of the procedure are bound to the arguments to which the procedure is applied.

These two rules describe the essence of the evaluation process, a basic cycle in which expressions to be evaluated in environments are reduced to procedures to be applied to arguments, which in turn are reduced to new expressions to be evaluated in new environments, and so on, until we get down to symbols, whose values are looked up in the environment, and to primitive procedures, which are applied directly.

– “The Metacircular Evaluator” from SICP


;; Determine what to do
(eval '(+ 1 2))

;; Do it
(apply #'+ '(1 2))

Rich Hickey Talks

core.async Channels

Full talk: infoq.com/presentations/clojure-core-async/

Inside core.async Channels

Simple Made Easy

Clojure

Hyper Tutorial

In the smallest nutshell, here’s how you can hit the ground running with Clojure.

Types

123  ; number (long)
"a string"
:keywords
'symbols

Data Structures

'(1 2 3)     ; list
[1 2 3]      ; vector
#{1 2 3}     ; set
{:a 1, :b 2} ; map

It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.

– Alan Perlis6

Syntax

(operator operand operand operand)

All Clojure operations have the same syntax: opening parenthesis, operator, operands, closing parenthesis”

– Daniel Higginbotham1

Tips

Installation

This is easiest on Linux or Mac7 if you’ve already got asdf installed.

sudo apt-get install rlwrap leiningen

echo "Installing Java"
asdf plugin-add java https://github.com/halcyon/asdf-java.git
asdf install java semeru-openj9-21.0.5+11_openj9-0.48.0
asdf global java semeru-openj9-21.0.5+11_openj9-0.48.0
java -version

echo "Installing Clojure"
asdf plugin add clojure https://github.com/asdf-community/asdf-clojure.git
asdf install clojure latest
asdf global clojure latest
clj -version

I like to use the IBM Semeru8 runtimes, which are designed for hybrid-cloud and containerized applications. There are great Docker containers9 available to use for free. For a time, I worked within the IBM Software Lab in Markham where these tools were developed, and crossed paths with many people on the compiler teams.

echo "Installing Amazon Corretto JVM"
asdf install java corretto-21.0.6.7.1
asdf global java corretto-21.0.6.7.1

The Amazon Corretto JVM is also great:

Common Clojure Tasks

Using Libraries

In this guide, I’ll be including libraries as-used with the `require` function as needed.

(require '[clj-http.client :as client])

In your projects, you’ll need to use a project/dependency manager like lein or deps to download dependencies and make them available in your Clojure project and REPL. After installing dependencies, they can be included within your Clojure namespaces like so:

(ns my-app.core
  (:require [clj-http.client :as client]
            [my-app.readers :refer [rss qr-img]]
            [my-app.nuclear :as n]
            [my-app.platform.sidewinder :as sw]))

This manual does load a few libraries by default, but generally I will use the former requirement format when demonstrating the use of a new library.

Querying HTTP APIs

It is easy to fetch data using the clj-http library.

(require '[clj-http.client :as client])
(client/head "https://ryanfleck.ca")

;; Result:
'(:cached   :request-time 197  :repeatable? false
            :protocol-version (:name "HTTP"  :major 1  :minor 1)
            ;; ... more stuff ...
            :headers ("referrer-policy" "strict-origin-when-cross-origin"
                      "Server" "cloudflare"
                      "Content-Type" "text/html; charset=utf-8"
                      "x-content-type-options" "nosniff"  "alt-svc" "h3=\":443\"; ma=86400"
                      "NEL" "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}"
                      "Connection" "close"  "cf-cache-status" "DYNAMIC"  "CF-RAY" "8fedb5dbee3cebbe-SEA"

                      ;; ... more stuff ...
                      "Cache-Control" "public, max-age=0, must-revalidate")
            :orig-content-encoding "gzip"  :status 200
            :length 0  :body   :trace-redirects ())

Group-By

The amazing group-by function allows you to group data by a common key. My use case for this was grouping articles in different languages collected over time. Here’s what the incoming data looked like:

{:count 260, :hour 2025-01-07T21:00, :language "bn"}
{:count 100, :hour 2025-01-07T21:00, :language "de"}
{:count 1041, :hour 2025-01-07T21:00, :language "es"}
{:count 211, :hour 2025-01-07T21:00, :language "fa"}
{:count 1, :hour 2025-01-07T21:00, :language "fi"}
{:count 268, :hour 2025-01-07T21:00, :language "fr"}
{:count 63, :hour 2025-01-07T21:00, :language "gu"}
;; ... data truncated ...

Here is how the data looks after using group-by:

(group-by :language (db/get-items-by-hour-72h-langs))

{"nl" [{:count 3, :hour #object[java.time.LocalDateTime 0x2e063d23 "2025-01-07T21:00"], :language "nl"}
       {:count 2, :hour #object[java.time.LocalDateTime 0x5080c1d3 "2025-01-09T11:00"], :language "nl"}
       {:count 1, :hour #object[java.time.LocalDateTime 0x2cef6527 "2025-01-09T21:00"], :language "nl"}],
 "pt" [{:count 188, :hour #object[java.time.LocalDateTime 0x6e9352c2 "2025-01-07T21:00"], :language "pt"}
       {:count 175, :hour #object[java.time.LocalDateTime 0x41f9af3f "2025-01-08T11:00"], :language "pt"}
       {:count 62, :hour #object[java.time.LocalDateTime 0x71df170a "2025-01-09T15:00"], :language "pt"}
       {:count 96, :hour #object[java.time.LocalDateTime 0x58aa8fa8 "2025-01-09T21:00"], :language "pt"}],
 "en" [{:count 4412, :hour #object[java.time.LocalDateTime 0x74f18d18 "2025-01-07T21:00"], :language "en"}
       {:count 2552, :hour #object[java.time.LocalDateTime 0x3fd9a0d6 "2025-01-09T11:00"], :language "en"}
       {:count 227, :hour #object[java.time.LocalDateTime 0x6fa4cc34 "2025-01-09T13:00"], :language "en"}
       {:count 856, :hour #object[java.time.LocalDateTime 0x4a64b22a "2025-01-09T21:00"], :language "en"}],
 "ur" [{:count 100, :hour #object[java.time.LocalDateTime 0x552a7e60 "2025-01-07T21:00"], :language "ur"}
;; ... data truncated ...

Caching Return Values (Memoization)

We can use core.memoize to cache values with a variety of methods.

(defn get-72h-data []
  (let [data (c/extract-series {:x :hour :y :count} (db/get-items-by-hour-72h))
        series {"Collected Items" [(map #(localDateTime->Date %) (:x data)) (:y data)]}]

    series))

(def one-minute-in-ms (* 60 1000))
(def get-72h-data-memoized (memo/ttl get-72h-data {} :ttl/threshold one-minute-in-ms))

By calling the variable we have defined, we can see the dramatic reduction in time on the second execution.

(time (get-72h-data-memoized)) ; => "Elapsed time: 17.726885 msecs"
(time (get-72h-data-memoized)) ; => "Elapsed time: 0.05838 msecs"

HTML - Reading, Transforming, Templating

Tranforming Dart

Rendering Charts

Here’s a short guide on one method of many to render charts in web apps. The Apache echarts library has a getting started graph we can use as an example.

(defn get-72h-echart-body []
  (log/debug "Attempting to return HTML for new EChart")
  (parser/render-file "graphs/72h-echart.html"
                      {:width 800
                       :height 500
                       :data {:title {:text "ECharts Getting Started Example"}
                              :tooltip {}
                              :legend {:data ["sales"]}
                              :xAxis {:data ["Shirts" "Cardigans" "Chiffons" "Pants" "Heels" "Socks"]}
                              :yAxis {}
                              :series [{:name "Sales" :type "bar" :data [5 20 36 10 10 20]}]}}))

Using Selmer with the template below yields the same chart as the one in the demo.

<h3>72h Echart</h3>
<div id="72h-echart-main" style="width: {{ width }}px; height: {{ height }}px;"></div>
<script type="text/javascript">
  // Initialize the echarts instance based on the prepared dom
  var myChart = echarts.init(document.getElementById('72h-echart-main'));

  // Specify the configuration items and data for the chart
  var option = {{ data|json|safe }};

  // Display the chart using the configuration items and data just specified.
  myChart.setOption(option);
</script>

I realized that this library is fairly popular in the Clojure community after discovering it myself, and for good reason - using Echarts is a highly data-driven experience that requires only a touch of client javascript and can otherwise be entirely created with Clojure data structures. Here are some other places Apache Echarts are used:

  1. Scicloj - Noj notebooks

HTMX and Simple Web Applications

Using HTMX provides a myriad of benefits and few drawbacks.

You do need to write a backend that returns html, as text, and not JSON. The benefit of this - you don’t need to spend an additional few steps transforming and interpreting JSON on your frontend.

Useful Snippets

Load and swap out something small, like a clock, every second:

<span hx-get="/api/now" hx-trigger="load, every 1s" hx-swap="innerHTML"></span>

On the backend, this is the code - essentially we just send back some text in a span.

["/now"
 {:get {:summary "returns the current time as a span"
        :responses {200 {:body string?}}
        :headers {"Content-Type" "text/html"}
        :handler (fn [_params]
                   {:status 200
                    :body (str "<span>" (time/dateline-utc) "</span>")})}}]

Edge Cases

Notably on hx-swap any scripts included on a page won’t run, but those included during an hx-get will be as long as they aren’t in the root in which case they will be commented out. I learned this the hard way.

<!-- *None* of these will run if a page is just swapped in. -->
<div>
  <script>console.log("in a div, this runs.");</script>
</div>

<span>
  <script>console.log("in a span, this runs.");</script>
</span>

<script>console.log("outside, this runs.");</script>

Notes: Clojure for the Brave and True +

This is a good Clojure textbook. The sections below are roughly the same as the book, but are rearranged and include extra material where I found it useful in my learning journey. The PLUS (+) indicates this - that I have taken liberty to include additional info where I wanted.

All quotes in this section are from this material.

Chapter 3: Do Things

Do Things: A Clojure Crash Course

Clojure uses the familiar LISP S-Expressions. Literals are valid forms - each of these will just return itself. All of these types build off common Java primitives and data structures.

1
"a string"
["a" "vector" "of" "strings"]
{ :a "map" :of "stuff"}
1
“a string”
[“a” “vector” “of” “strings”]
{:a “map”, :of “stuff”}

Clojure uses whitespace to separate operands, and it treats commas as whitespace.

Good old s-expressions:

(operator operand1 operand2 etc)

Clojure’s structural uniformity is probably different from what you’re used to. In other languages, different operations might have different structures depending on the operator and the operands. For example, JavaScript employs a smorgasbord of infix notation, dot operators, and parentheses. Clojure’s structure is very simple and consistent by comparison. […] No matter which operator you’re using or what kind of data you’re operating on, the structure is the same.

Control Flow

Key Functions:

(def boolean-value true)
(if boolean-value "It's true!" "Lol nope") ; "It's true!"
(when boolean-value "Yes") ; "Yes"
(when-not boolean-value "Nope")

When and when-not enable execution of a form when a value is true (or false for when-not) without providing a false-case like an if statement.

Do enables the combination of multiple forms - it will return the result of the final form. This is very useful for logging or running multiple simple statements within an if expression.

(do (+ 1 2) (+ 3 4) (+ 5 6))
;; =>  11

Boolean Mathematics & Truthiness

Key concepts:

(nil? 1)       ;; => false
(nil? nil)     ;; => true
(true? true)   ;; => true
(false? true)  ;; => false
(true? nil)    ;; => false - nil is falsey

Or returns the first truthy value or the last value:

(or nil false :cry :rage :fight :death)
;; =>  :cry

And returns the first falsey value or the last truthy value:

(and true 123 :kick :drown false)
;; =>  false

Assignments

Use clojure.core/def to bind names in Clojure.

Notice that I’m using the term bind, whereas in other languages you’d say you’re assigning a value to a variable. Those other languages typically encourage you to perform multiple assignments to the same variable.

However, changing the value associated with a name like this can make it harder to understand your program’s behavior because it’s more difficult to know which value is associated with a name or why that value might have changed. Clojure has a set of tools for dealing with change, which you’ll learn about in Chapter 10. As you learn Clojure, you’ll find that you’ll rarely need to alter a name/value association.

(def status :my-body-is-ready)
;; =>  #'org.core/status

Types

{:numbers [ 1 2/3 4.5 ]
 :strings ["Yep" "With escapes! -> \""] }
:numbers(1 2/3 4.5):strings(Yep With escapes! -> “)
:keywords
'symbols

Primitive Data Structures (Collections)

Clojure supports four literal collection types:

'(1 2 3)     ; list
[1 2 3]      ; vector
#{1 2 3}     ; set
{:a 1, :b 2} ; map

Maps

clojure.core/get allows you to grab keys, and can return nil or a default:

(get {:x 1 :y 2} :y)   ;; => 2
(get {:x 1 :y 2} :z)   ;; => nil
(get {:x 1 :y 2} :z 3) ;; => 3

clojure.core/get-in allows you to dig into nested maps:

(get-in
  {:head 1 :chest {:ribs 10 :cavity {:heart "pumpin'" :lungs 2}}}
  [:chest :cavity :heart])
;; =>  pumpin'

You can use a map like a function:

({:what "in" :tar "nation?"} :tar)
;; =>  nation?

…and keywords can be used the same way with a few data structures:

(:tar {:what "in" :tar "nation?"})
;; =>  nation?
(:far {:what "in" :tar "nation?"} "no far")
;; =>  no far

Vectors - clojure.core/vec

Vectors are zero-indexed collections like arrays.

(def vec1 [1 2 3 4 5])
(get vec1 0) ;; => 1

You can use vector to make vectors and conj to add to them:

(def vec2 (vector :weather :is :nice))
(conj vec2 :today) ;; => [:weather :is :nice :today]

Lists - clojure.core/list

Recall that Clojure is a LISP. Lists can hold anything. Use a tick mark to indicate that a sexp is a list.

(def list1 '(1 2 3 4 5))
(nth list1 3)  ;; => 4

Using clojure.core/conj on a list adds items to the beginning, and on a vector will add items to the end. A bit of a footgun.

(conj list1 0) ;; => (0 1 2 3 4 5)

Hash Sets and Sorted Sets

Brave Clojure: Sets

(def hs1 #{"this is a hash-set" 19 :testing})

A hash set can only store unique values. Using conj to add to a hash-set will combine unique values.

(conj hs1 19)
;; =>  #{"this is a hash set" 19 :testing}
(hash-set 1 2 3 4 1 2 3 4 5 6)
;; =>  #{1 4 6 3 2 5}

Usefully, set can be used to derive all the unique values from another collection.

(set [1 2 3 4 1 2 3 3 4 1 2 3 4 2 3 2 1 2])
;; =>  #{1 4 3 2}

Use clojure.core/get and clojure.core/contains? with hash sets:

(contains? hs1 18) ; false
(contains? hs1 19) ; true
(get hs1 18) ;; => nil
(get hs1 19) ; 19

Calling Functions

Because of Clojure’s Lisp syntax, with the humble and incredibly simple s-expression as the core building block of a Lisp program, we can do some pretty incredible things to simplify complex operations.

(operator operand operand operand)

All Clojure operations have the same syntax: opening parenthesis, operator, operands, closing parenthesis”

– Daniel Higginbotham1

Also recall that we can return functions:

(or + - * /)
;; =>  #function[clojure.core/+]

Which means we can return a function and call it on more data:

((or + -) 1 2 3)
;; =>  6

The error cannot be cast to clojure.lang.IFn indicates you are trying to use a number, string, or other type as a function.

("why" 1 2 3)
;; =>  class java.lang.String cannot be cast to class clojure.lang.IFn

Also see macro calls and special forms.

Clojure has no privileged functions. + is just a function, - is just a function, and inc and map are just functions. They’re no better than the functions you define yourself. So don’t let them give you any lip! More important, this fact helps demonstrate Clojure’s underlying simplicity. In a way, Clojure is very dumb. When you make a function call, Clojure just says, “map? Sure, whatever! I’ll just apply this and move on.” It doesn’t care what the function is or where it came from; it treats all func- tions the same. At its core, Clojure doesn’t give two burger flips about addi- tion, multiplication, or mapping. It just cares about applying functions.

– Daniel Higginbotham1

Defining Functions

(defn my-function
  "This is a docstring (yes, a JavaDoc docstring) to explain the function."
  [a b c]    ; <-- parameters
  (+ a b c))
;; =>  #'org.core/my-function
(my-function 1 2 3)
;; =>  6

Above is a simple example of defn, the function definition macro. A function must be defined with zero or more arguments and at least one clause in the function body. There are a few different ways to define a function that takes multiple arguments.

clojure.core/defn

Anonymous Functions

For one-offs or lambdas you can use clojure.core/fn . The extra space below is only present to highlight where the function is defined.

( (fn [x] (* x 2))  4)
;; =>  8

Even more condensed syntax exists to accomplish the same purpose.

( #(* % 2)  4)
;; =>  8

Here, % is used to represent the first argument to a function. If you need multiple arguments you can use %1, %2, %3, or %& for rest.

Multi Arity Functions

(defn hello
  "Provides a greeting to the user."
  ([name]
   (str "Hello, " name " - Welcome!"))
  ([name prefix]
   (str "Hello " prefix " " name))
  ([name prefix suffix]
   (str "Hello " prefix " " name ", " suffix "!")))
;; =>  #'org.core/hello
(hello "Ryan")
;; =>  "Hello, Ryan - Welcome!"
(hello "Ryan" "Mr." "Master of Ceremonies")
;; =>  "Hello Mr. Ryan, Master of Ceremonies!"

Providing a number of function bodies given different arguments is an easy way to group a similar collection of functions behind one name.

Variable Arity Functions

An ampersand (&) stores the remainder of arguments in a list.

(defn show_rest
  [first & rest_of_args]
  (str "First: " first " + rest: " rest_of_args))
;; =>  #'org.core/show_rest
(show_rest 1 2 3 4)
;; =>  "First: 1 + rest: (2 3 4)"

Multimethods

A multimethod enables a programmer to run an initial function against some data to determine which function it should eventually be passed to.

(defmulti get-dose
  "With weight in Kg, provide the adult dose of a medicine."
  (fn [data] (:medicine data)))

(defmethod get-dose :ibuprofen
  [data]
  (str "Up to " (* (:weight data) 10) "mg / 4hr"))

(defmethod get-dose :acetominophen
  [data]
  (str "Up to " (* (:weight data) 5) "mg / 4hr"))
(get-dose {:medicine :ibuprofen :weight 200})
;; =>  "Up to 2000mg / 4hr"
(get-dose {:medicine :acetominophen :weight 200})
;; =>  "Up to 1000mg / 4hr"

Destructuring

To destructure is to pull data out of a data structure within the arguments of a function. This is one of the really magical things I enjoyed when first learning Elixir. It saves a lot of time and prevents the first few lines of a function being full of first, get-in, etc.

(defn second-thing [[a b]] b)
(second-thing [1 2 3])
;; =>  2

…as you can see, the exact length of the vector is not strictly pattern-matched, but the first two arguments are captured and available in the function as arguments. You can use & rest in this destructuring syntax to get the remainder of arguments.

For maps, these formats are equivalent:

[{key1 :key1 key2 :key2}]
[{:keys [key1 key2]}]

You can use the :as key to also make the entire vector/map available.

[{key1 :key1 key2 :key2 :as data}]
[{:keys [key1 key2] :as data}]
[[a b :as data]]
(defn test1 [{key1 :key1 key2 :key2 :as mapdata} [a b :as vecdata]]
  (str "Map: " mapdata " - Vec: " vecdata))
(test1 {:key1 "wow" :key2 "whoa"} [:first :second :third :fourth])
;; =>  "Map: {:key1 \"wow\", :key2 \"whoa\"} - Vec: [:first :second :third :fourth]"

These approaches can also be heavily nested. If there is a map within a list that contains a map, this can be destructured.

Closures

Functions that are returned from other functions with encapsulated data are called closures.

(defn declare-pilled
  "Returns a string generator stating that the input is thing-pilled"
  [thing]
  (fn [x] (str "Wow, " x " sure is " thing "pilled.")))
(def that-is-cringe (declare-pilled "cringe"))
(that-is-cringe "watching TV")
;; =>  "Wow, watching TV sure is cringepilled."

Did you pick the name based on starting with the word “closure” and replacing the “s” with “j” for Java? It seems pretty likely, but it would be nice to have that confirmed.

The name was chosen to be unique. I wanted to involve c (c#), l (lisp) and j (java). Once I came up with Clojure, given the pun on closure, the available domains and vast emptiness of the googlespace, it was an easy decision.

– Rich Hickey12

Binding Variables

You can use clojure.core/let like let* from elisp. It allows you to bind variables within its lexical scope - a fancy way of stating ‘within the structure that it was created inside’.

(let [a 1
      b 2]

  (+ a b))
;; =>  3

Loops and Recursion

Far more often you’ll be using map, filter, and reduce to perform operations on an array, but Clojure includes a loop/recur construct to improve the performance of what otherwise would be a very standard inner and outer pair forming a recursive function.

See clojure.core/loop and clojure.core/recur .

(loop [i 0
       list []]
  (if (< i 10)
    (recur (+ i 1)
           (conj list i))
    list))
;; =>  [0 1 2 3 4 5 6 7 8 9]

Looking closely, you’ll see these two important statements:

First - loop works like a let and enables the definition of initial variables. In this case, i is set to 0 and list holds an empty list.

(loop [i 0
       list []]

Second - the recur statement kicks execution back up to the beginning of the loop with the provided elements as the new

(recur (+ i 1)         ; bound to 'i'
       (conj list i))  ; bound to 'list'

Using loop is a cleaner and more performant approach to writing loops than recursion as used in traditional functional languages.

Map, Filter, Reduce

The three kings of list processing methods. Learn to use them and profit!

(map (fn [x] (* x 2)) [1 2 3 4 5 6])
;; =>  (2 4 6 8 10 12)
(filter (fn [x] (> x 7)) [2 4 6 8 10 12])
;; =>  (8 10 12)
(reduce (fn [a b] (+ a b)) [1 2 3 4 5 6])
;; =>  21

You may provide reduce with an initial value, which will be used instead of the first two elements in the sequence.

(reduce (fn [a b] (+ a b)) 10 [1 2 3 4 5 6])
;; =>  31

The reduce function is meant, in general, to build results. This does not mean it has to return a single primitive. Per the book,1 it is clearer to use reduce when building a result than loop, which forces a user to more deeply inspect your code to understand its purpose.

(reduce (fn [a b] {:name b :child a}) :none ["Mark" "Luke" "John"])
;; =>  {:name "John", :child {:name "Luke", :child {:name "Mark", :child :none}}}

Deployment

Clojure, when compared to some other platforms, is fairly easy to deploy - both on bare metal and containerized. The JVM has been deployed on unknowable billions of machines at this point, and its properties are well understood.

Docker

Let’s take a look at what it would take to get a web app called coolzone up and running on your server with docker and Traefik installed. You’ll need to write a Dockerfile and docker-compose.yml to define the container for your app and how to bring it up and network it.

--> Dockerfile

# Run a multi-stage build
# 1. Build JAR in clojure:lein
# 2. Leave JAR in finished JRE container
FROM clojure:lein

# Create App Directory
RUN mkdir -p /app
WORKDIR /app

# Get Dependencies (Cached as long as project.clj is unchanged)
COPY project.clj /app
RUN lein deps

# Build UberJar
COPY . /app
RUN lein uberjar

# Multi-Stage Build - Run in IBM Semeru
# See: https://hub.docker.com/_/ibm-semeru-runtimes
# Logging: https://luminusweb.com/docs/logging.html
FROM ibm-semeru-runtimes:open-21-jdk

RUN mkdir /opt/app
COPY --from=0 /app/target/uberjar/coolzone.jar /opt/app
EXPOSE 3000

CMD ["java", "-jar", "/opt/app/coolzone.jar"]

--> docker-compose.yml

services:
  coolzone:
    build:
      context: .
      dockerfile: "Dockerfile"
    image: coolzone_production
    env_file: "prod.env" # set environment variables here.
    restart: unless-stopped
    volumes:
      - ~/Coolzone/data:/data/files:rw
    ports:
      # external:internal
      - "7598:3000"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.coolzone.rule=Host(`coolzone.ryanfleck.ca`)"
      - "traefik.http.services.coolzone.loadbalancer.server.port=3000"
      - "traefik.http.routers.coolzone.service=coolzone"
      # Note: Some Traefik labels removed for security.
    networks:
      - web

networks:
  web:
    external: true

--> prod.env

PROD="true"
PORT=3000
DATA_DIR="/data/files"
DATABASE_URL="postgresql://whatever..."
SERVICE_KEY="jlkhl76098798d5gsjero2ih-asdufoi45lsf..."

At this point all you need to do is pull your repository and run:

docker-compose up -d --build coolzone

To launch your container and troubleshoot run:

docker-compose run coolzone sh

Optimization & JVM Bytecode

Emacs

Emacs is my editor of choice. It has unbeatable support for LISPs.

Setup

My personal configuration is based off of the sensible defaults provided in the Clojure for the Brave and True textbook. Using the initialization files mentioned on the linked page is a great way to start using Emacs in general.

Keep in mind that the more crap you have in your classpath, the longer CIDER will take to start up.

Command Cheat Sheet

CommandAction
C-c C-x C-x RETStart CIDER
M-x ciderPrompts for more options
M-x cider-jack-inJacks in to current Clojure (clj) project
C-c C-zJump cursor to REPL
C-u C-c C-zJump cursor to REPL and switch to file namespace
C-c C-d acider-apropos to remember var names
C-x 5 2Pop out buffer into new window
C-c C-kEvaluate buffer (handy)
C-c C-eEvaluate preceding form
C-c C-c or C-M-xEvaluate current top-level form
C-u C-c C-cEvaluate current top-level form in debug mode
C-c C-v rEvaluate highlighted region
C-c C-bInterrupt evaluation
M-.cider-find-var: Warp to definition under cursor
C-c C-d dLook up documentation for current form
C-c C-mmacroexpand-1: Macroexpand the form at point
C-c M-zEval current buffer and switch to relevant REPL
C-c M-n rReload all files on classpath
M-,Return to your pre-jump location
M-TABComplete the symbol at point
C-c C-qQuit CIDER

Sources:

  1. Cider Docs: Basic Workflow
  2. Experience

Cider

CIDER is an interactive programming environment for Clojure.

Traditional programming languages and development environments often use a Edit, Compile, Run Cycle. In this environment, the programmer modifies the code, compiles it, and then runs it to see if it does what she wants. The program is then terminated, and the programmer goes back to editing the program further. This cycle is repeated over and over until the program behavior conforms to what the programmer desires.

Using CIDER’s interactive programming environment, a programmer works in a very dynamic and incremental manner. Instead of repeatedly editing, compiling, and restarting an application, the programmer starts the application once and then adds and updates individual Clojure definitions as the program continues to run.13

It looks like this when run - the explanation given below is a great introduction to much of the built-in functionality made available to the user.

;; Connected to nREPL server - nrepl://localhost:36099
;; CIDER 1.13.0-snapshot (package: 20231127.825), nREPL 1.0.0
;; Clojure 1.11.1, Java 17.0.9
;;     Docs: (doc function-name)
;;           (find-doc part-of-name)
;;   Source: (source function-name)
;;  Javadoc: (javadoc java-object-or-class)
;;     Exit: <C-c C-q>
;;  Results: Stored in vars *1, *2, *3, an exception in *e;
;; ======================================================================
;; If you’re new to CIDER it is highly recommended to go through its
;; user manual first. Type <M-x cider-view-manual> to view it.
;; In case you’re seeing any warnings you should consult the manual’s
;; "Troubleshooting" section.
;;
;; Here are a few tips to get you started:
;;
;; * Press <C-h m> to see a list of the keybindings available (this
;;   will work in every Emacs buffer)
;; * Press <,> to quickly invoke some REPL command
;; * Press <C-c C-z> to switch between the REPL and a Clojure file
;; * Press <M-.> to jump to the source of something (e.g. a var, a
;;   Java method)
;; * Press <C-c C-d C-d> to view the documentation for something (e.g.
;;   a var, a Java method)
;; * Print CIDER’s refcard and keep it close to your keyboard.
;;
;; CIDER is super customizable - try <M-x customize-group cider> to
;; get a feel for this. If you’re thirsty for knowledge you should try
;; <M-x cider-drink-a-sip>.
;;
;; If you think you’ve encountered a bug (or have some suggestions for
;; improvements) use <M-x cider-report-bug> to report it.
;;
;; Above all else - don’t panic! In case of an emergency - procure
;; some (hard) cider and enjoy it responsibly!
;;
;; You can remove this message with the <M-x cider-repl-clear-help-banner> command.
;; You can disable it from appearing on start by setting
;; ‘cider-repl-display-help-banner’ to nil.
;; ======================================================================

Manual Editing Suite: Emacs, Ox-Hugo, Cider

There is some setup required to execute Clojure inline within this org-mode file and cleanly transform it to markdown.

Ox-Hugo must be added to Emacs.

There is a minimal amount of emacs lisp added to my editor to support this. The remainder of setup is completed in the front matter of the org file, clj.org, in /content-org/languages/clj.org.

(setup (:package ox-hugo)
  (:load-after ox))

(defun clojuredoc-string-to-url    (str)
    "In a url, ? becomes _q, replace these in the url part of STR in clojuredocs links."
   (s-replace "?" "_q" str))

(defun org-link-to-clojuredocs ()
  "Insert a link to clojuredocs.org."
  (interactive)
  (let ((str (read-string "Function (ex. clojure.core/when) >> ") ))
    (insert (s-concat "[[https://clojuredocs.org/"
                      (clojuredoc-string-to-url str) "][" str "]]"))))

(defun org-link-to-core-clojuredocs ()
  "Insert a link to clojuredocs.org in the clojure.core namespace."
  (interactive)
  (let ((str (read-string "clojure.core function (ex. when) >> ") ))
    (insert (s-concat "[[https://clojuredocs.org/clojure.core/"
                      (clojuredoc-string-to-url str) "][clojure.core/" str "]]"))))

;; Make these easier to type
(global-set-key (kbd "C-c o C") 'org-link-to-clojuredocs)
(global-set-key (kbd "C-c o c") 'org-link-to-core-clojuredocs)

A bunch of additional front matter must be added in order for ox-hugo to correctly move the file and add the appropriate front matter. Care has been taken to ensure the output format is readable.

#+LAYOUT: docs-manual
#+TITLE: Clojure
#+SUMMARY: Enterprise grade magick.
#+hugo_base_dir: ../../
#+hugo_section: languages
#+hugo_custom_front_matter: :warning "THIS FILE WAS GENERATED BY OX-HUGO, DO NOT EDIT!!!"
#+hugo_custom_front_matter: :toc true :summary "Enterprise grade magick." :chapter true
#+hugo_custom_front_matter: :aliases '("/clj/" "/clojure/" "/clj" "/cljd" "/cljs")
#+PROPERTY: header-args :eval no :exports both
#+hugo_level_offset: 0

…apart from an issue where I can’t have headers beyond level 3, which I still must resolve, ox-hugo has happily enabled me to leverage all the enhanced markdown processing features provided by hugo (render hooks in particular) while still writing and executing code in ORG.

Obligatory Bell Curve Meme

As an ex-React, ex-Django, ex-Typescript, ex-Express developer, happy to never return unless a good reason / cool project emerges, I am fully qualified to throw all this shade. The technologies clowned upon below all have their place, but due to their popularity and ease of ChatGPT’ing together codebases, are prone to turning into a strange sort of insecure muck.

Despite all the advantages of LISPs, wonderful systems are regularly built in a variety of languages. The JVM is not a good fit for many applications. Clojure is great, but don’t be a forceful evangelist or you’ll end up like the guy in the middle of this meme. It’s the worst type of programmer you could be.

Good programming is a certain mindset and wisdom - not a language.

Resources

Websites:

  1. Clojure for the Brave and True
  2. Clojure on Exercism (Challenges)
  3. Luminus (Web ‘Framework’)
  4. Clojure Job Board
  5. Clojure Regex Tutorial
  6. Newest ‘Clojure’ Questions on Stack Overflow
  7. Clojure on Stack Overflow
  8. Clojure Slack Channel
  9. Org-Babel Clojure (Literate Programming) and (use case - try out libraries)
  10. Quil: Animations in Clojure
  11. SciCloj - Clojure Data Science Community , and their Noj project

Books:

(Remember to buy books to support good authors.)

  1. Dmitri Sotnikov, Scot Brown: Web Development with Clojure: Build Large, Maintainable Web Applications Interactively, 3e, 2021, ISBN: 168050682X, 9781680506822
  2. Renzo Borgatti: Clojure, The Essential Reference, 0e, 2021, ISBN: 9781617293580, 6664843499, 1447772004, 161729358X
  3. Kleppmann, Martin: Designing data-intensive applications: the big ideas behind reliable, scalable, and maintainable systems, 1e2p, ISBN: 9781449373320, 1449373321

  1. “Clojure for the Brave and True” by Daniel Higginbotham, braveclojure.com  ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. “A History of Clojure”, Rich Hickey, dl.acm.org PDF  ↩︎ ↩︎

  3. “Companies using Clojure or ClojureScript” clojure.org  ↩︎

  4. “The Curse of Lisp - Clojure vs Lisp” https://www.freshcodeit.com/blog/myths-of-lisp-curse#clojure-vs-lisp-acquired-and-inherited-traits  ↩︎

  5. “Literate Programming”, Donald E. Knuth, stanford.edu  ↩︎

  6. “Clojure for the Brave and True” page 48. ↩︎

  7. “How to use ASDF on MacOS”, Qing Wu, wiserfirst.com  ↩︎

  8. “Introducing the no-cost IBM Semeru Runtimes”, Mark Stoodley, developer.ibm.com  ↩︎

  9. “Docker Hub: IBM Semeru Runtimes”, hub.docker.com  ↩︎

  10. “Technology Compatibility Kit”, wiki  ↩︎

  11. “The Definitive Guide to Clojure on the JVM”, Eric Normand, ericnormand.me  ↩︎

  12. “Why is Clojure named Clojure?”, Alex K., stackoverflow.com  ↩︎

  13. Documentation for CIDER: Interactive Programming  ↩︎



Site Directory

Pages are organized by last modified.



Page Information

Title: Clojure
Word Count: 6780 words
Reading Time: 32 minutes
Permalink:
https://manuals.ryanfleck.ca/clj/