Ryan's Manuals

Clojure

Enterprise grade magick.


Contents



Philosophy

Improving Productivity

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

Startups should focus on: - Bottom-up programming - focusing on fine grained abstractions and composability - Avoiding frameworks and saas tools - Focus on individual programmer productivity

Thought: Rebuilding things from scratch sucks and Clojure potentially enables better code reuse.

Subverting the Lisp Curse

Programmers know the benefits of everything and the tradeoffs of nothing

– Rich Hickey

I've got a particular friend who has an obsession with Clojure - he is baffled as to why the entire world doesn't use it. When asked, he may say something like "they're all idiots!" While no sage myself, I do believe that the mentors I have had have wisely guided me regarding tool choice, and a variety of factors must be considered along with a problem statement.

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.

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 rumours that Clojure subverts this curse with two measures: understanding of the problem, and the ability to leverage Java libraries. 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 comment.

Rich Hickey Talks

core.async Channels

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

  • Problems and Premise

    • Function chains make poor machines
    • Reasonable programs are organized around processes and queues (conveyance must become first-class.)
    • Java.util.concurrent queues have lots of problems and costs
    • You should be able to add machines to make things scale
    • Sometimes logic relies on shared state

      • Objects don't fix this, they just put the shared state and functions in one place
      • Async/Await, Promises, Futures are all handoffs or call/returns
  • Solutions

    • Communicating Sequential Processes (CSP) (Hoare 1978) are the model for Clojure
    • Constructs:

      • channels are queue-like, multi-reader/writer, unbuffered or fixed buffers

        • Functions to put, take, close, etc.
      • thread gives you a real thread with real blocking
      • go is a logical software thread that can be parked during blocking calls
    • Friends don't let friends put logic in handlers.
    • Basically use channels to route your data through functions.

Inside core.async Channels

Simple Made Easy

Clojure for the Brave and True

All quotes in this section are from this material.

Literate Programming

I'll be using org-babel-clojure to write and run code within this manual directly. Learning, remembering, and teaching now mix.

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

If the result is more complex, like a map, it'll be in a table and it's tougher to target with css so I'll be lazy for now and leave it sort of unstyled:

(vals {:a 1 :b 2})
(keys {:c 3 :d 4})
(1 2)
(:c :d)

…that's OK.

Chapter 3: Do Things

Do Things: A Clojure Crash Course

Clojure uses the familiar LISP S-Expressions. Literals are valid forms.

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)

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

Enabled with if, cond, when, when-not, and other functions.

(def boolean-value true)

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

When allows you to execute a form when a value is true and not provide a false-case like an if statement.

Boolean Mathematics

(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 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

Data Structures

Clojure supports four literal collection types:

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

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

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

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

Recall that Clojure is a LISP. Lists can hold anything.

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

Using conj on a list adds items to the beginning:

(conj list1 0) ;; => (0 1 2 3 4 5)
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)
(set [1 2 3 4 1 2 3])
#{1 4 6 3 2 5}
#{1 4 3 2}

Use get and contains? with hash sets:

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

Luminus

New Project

Upon creating and generating a new Luminus project and running it once in the REPL, here is part of the tree of directories and files that is generated:

guestbook/
│  
├── project.clj
│  
├── resources
│   ├── docs
│   │   └── docs.md
│   ├── html
│   │   ├── about.html
│   │   ├── base.html
│   │   ├── error.html
│   │   └── home.html
│   ├── migrations
│   │   ├── 20240223181041-add-users-table.down.sql
│   │   └── 20240223181041-add-users-table.up.sql
│   ├── public
│   │   ├── css
│   │   │   └── screen.css
│   │   ├── favicon.ico
│   │   ├── img
│   │   │   └── warning_clojure.png
│   │   └── js
│   └── sql
│       └── queries.sql
├── src
│   └── clj
│       └── guestbook
│           ├── config.clj
│           ├── core.clj
│           ├── db
│           │   └── core.clj
│           ├── handler.clj
│           ├── layout.clj
│           ├── middleware
│           │   └── formats.clj
│           ├── middleware.clj
│           ├── nrepl.clj
│           └── routes
│               └── home.clj
├── test
│   └── clj
│       └── guestbook
│           ├── db
│           │   └── core_test.clj
│           └── handler_test.clj
│  
└── test-config.edn

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.

Command Cheat Sheet

CommandAction
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
C-c C-eEvaluate preceding form
C-c C-c or C-M-xEvaluate current top-level form
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

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.1

It looks like this when run:

;; 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.
;; ======================================================================

Resources

Websites:

  1. Clojure for the Brave and True

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



Site Directory



Page Information

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