Skip to content

a comprehensive, hands-on study of the Clojure programming language, documenting all concepts and implementing code examples in a detailed Literate Programming notebook

Notifications You must be signed in to change notification settings

anoopemacs/Getting-Clojure

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cover

“In the Wild” realworld code examples

Aside, Official docs:- cheatsheet-usletter-color.pdf clojure-api-docs/api-index.html

Part I ---------------------------------------------------------------------------------------------------

Hello, Clojure

The Very Basics

(println "Hello, World")
;; => nil
(str "Clo" "jure")
;; => "Clojure"
(str "Hi" 007)
;; => "Hi7"
(count "Anoop")
;; => 5
(println true)
;; => nil
(println false)
;; => nil

Arithmetic

(+ 1900 84)
;; => 1984
(* 3.14 3.14)
;; => 9.8596

prefix notation (<verb> arg1 arg2 ...)

Comments

#_ can be used to comment out a sexp.

def

(def first-name "Russ")
;; => #'user/first-name

binds a symbol with a value

all-lower-case-with-words-seperated-by-dashes convention, aka kebab-case

A Function of Your Own

(defn hello-world [] (println "Hello, world!"))
;; => #'user/hello-world
(hello-world)
;; => nil
(defn hello-someone [name]
  (println "Hello" name))
;; => #'user/hello-someone
(hello-someone "Anoop")
;; => nil

(defn average [a b]
  (/ (+ a b) 2.0))
;; => #'user/average
(average 5 10)
;; => 7.5

In the Wild

Created a lein project called blottsbooks and ran it.

:gen-class means compile this namespace

you need to define a function before you can use it therefore, clojure codebases tend to read bottom up

Staying out of trouble

(/ 100 0)
;; => Execution error (ArithmeticException) at user/eval10074 (REPL:70).
;;    Divide by zero
(bla 2 3)
;; => Syntax error compiling at (readme.org:73:1).
;;    Unable to resolve symbol: bla in this context

functions are values

hello-world
;; => #function[user/hello-world]

Vectors and Lists

One thing after another

[1 2 3 4]
;; => [1 2 3 4]
[1 true "three"]
;; => [1 true "three"]
[1 ["hi" "there" 2] 3 println]
;; => [1 ["hi" "there" 2] 3 #function[clojure.core/println]]

A toolkit of functions for vectors

(vector 1 2 3)
;; => [1 2 3]
(vector)
;; => []
(def novels ["Emma" "Coma" "War and Peace"])
;; => #'user/novels
(count novels)
;; => 3
(first novels)
;; => "Emma"
(rest novels)
;; => ("Coma" "War and Peace")

rest always returns a sequence

(def year-books ["1491" "April 1865" "1984" "2001"])
;; => #'user/year-books
(def third-book (first (rest (rest year-books))))
;; => #'user/third-book
third-book
;; => "1984"

(nth year-books 2)                      ;zero based index
;; => "1984"
(year-books 2)
;; => "1984"

Growing your vectors

conj = conjunction cons = construct

novels
;; => ["Emma" "Coma" "War and Peace"]
(conj novels "Carrie")
;; => ["Emma" "Coma" "War and Peace" "Carrie"]
(cons "Carrie" novels)
;; => ("Carrie" "Emma" "Coma" "War and Peace")

Lists

'(1 2 3)
;; => (1 2 3)
'()
;; => ()
()
;; => ()

Lists are different from sequences. See Chapter 10.

'(1 "two" 3 4.0 [5 "five"])
;; => (1 "two" 3 4.0 [5 "five"])
(list 1 2 3)
;; => (1 2 3)
(def poems '("Illiad" "Odyssey" "Now we are six"))
;; => #'user/poems
(count poems)
;; => 3
(first poems)
;; => "Illiad"
(rest poems)
;; => ("Odyssey" "Now we are six")
(nth poems 2)
;; => "Now we are six"

Lists vs Vectors

Vectors behave like arrays. (But unlike arrays their representation is optimised for easy copy creation to satisfy clojure immutability.) Lists are linked list of cons cells

lists-vs-vectors

conj does things keeping efficiency in mind

poems
;; => ("Illiad" "Odyssey" "Now we are six")
(conj poems "Jabberwocky")
;; => ("Jabberwocky" "Illiad" "Odyssey" "Now we are six")
(def vector-poems ["Illiad" "Odyssey" "Now we are six"])
;; => #'user/vector-poems
(conj vector-poems "Jabberwocky")
;; => ["Illiad" "Odyssey" "Now we are six" "Jabberwocky"]

in general, prefer to use vectors over lists

Staying out of trouble

immutability

(def novels ["Emma" "Coma" "War and Peace"])
;; => #'user/novels
(conj novels "Jaws")
;; => ["Emma" "Coma" "War and Peace" "Jaws"]
novels
;; => ["Emma" "Coma" "War and Peace"]
(def more-novels (conj novels "Jaws"))
;; => #'user/more-novels

In the Wild

clostache pedestal/samples

Maps, Keywords and Sets

This Goes with That

map

{"title" "Oliver Twist" "author" "Dickens" "published" 1838}
;; => {"title" "Oliver Twist", "author" "Dickens", "published" 1838}

(hash-map "title" "Oliver Twist" "author" "Dickens" "published" 1838)
;; => {"author" "Dickens", "published" 1838, "title" "Oliver Twist"}

(def book {"title" "Oliver Twist" "author" "Dickens" "published" 1838})
;; => #'user/book
(get book "published")
;; => 1838
(book "published")
;; => 1838

Keywords

(def book
  {:title "Oliver Twist" :author "Dickens" :published 1838})
;; => #'user/book
(:title book)
;; => "Oliver Twist"

Changing your Map without changing it

book
;; => {:title "Oliver Twist", :author "Dickens", :published 1838}
(assoc book :page-count 362)
;; => {:title "Oliver Twist",
;;     :author "Dickens",
;;     :published 1838,
;;     :page-count 362}
book
;; => {:title "Oliver Twist", :author "Dickens", :published 1838}
(assoc book :page-count 362 :language "English")
;; => {:title "Oliver Twist",
;;     :author "Dickens",
;;     :published 1838,
;;     :page-count 362,
;;     :language "English"}
(dissoc book :page-count)
;; => {:title "Oliver Twist", :author "Dickens", :published 1838}

Vectors can be looked at as maps of keys as 0, 1, 2 … to respective indexed items of that vector. Therefore, assoc and dissoc also work on vectors

(def num0 ["zero" "one" "two" "three"])
;; => #'user/num0
(assoc num0 4 "four")
;; => ["zero" "one" "two" "three" "four"]
(dissoc num0 0)
;; => Execution error (ClassCastException) at user/eval10398 (REPL:54).
;;    class clojure.lang.PersistentVector cannot be cast to class clojure.lang.IPersistentMap (clojure.lang.PersistentVector and clojure.lang.IPersistentMap are in unnamed module of loader 'app')

^Doubt: Why isnt dissoc working on vectors?

book
;; => {:title "Oliver Twist", :author "Dickens", :published 1838}
(keys book)
;; => (:title :author :published)
(vals book)
;; => ("Oliver Twist" "Dickens" 1838)

Sets

(def genres #{:sci-fi :romance :mystery})
;; => #'user/genres
(def authors #{"Dickens" "Austen" "King"})
;; => #'user/authors
(def repeats-not-allowed #{"Dickens" "King" "Dickens"})
;; => Syntax error reading source at (REPL:74:55).
;;    Duplicate key: Dickens

Membership

(contains? authors "Austen")
;; => true
(contains? genres :bla)
;; => false

(authors "Austen")
;; => "Austen"
(genres :animated)
;; => nil

(:sci-fi genres)
;; => :sci-fi
(:animated genres)
;; => nil

(def more-authors (conj authors "Clarke"))
;; => #'user/more-authors
more-authors
;; => #{"King" "Dickens" "Clarke" "Austen"}
(conj more-authors "Clarke")
;; => #{"King" "Dickens" "Clarke" "Austen"}
(disj more-authors "King")
;; => #{"Dickens" "Clarke" "Austen"}

In the wild

The configuration accepted by clojure.java.jdbc is a map The results of db queries are maps

In general, clojure softwares are typically:- maps go in -> maps come out

(require 'clojure.java.jdbc)
;; => Execution error (FileNotFoundException) at user/eval10431 (REPL:112).
;;    Could not locate clojure/java/jdbc__init.class, clojure/java/jdbc.clj or clojure/java/jdbc.cljc on classpath.

^TODO couldnt try out the first example because I havent yet learn how to install external library. Come back to this later.

Membership test using literal set

(def city "Bombay")
;; => #'user/city
(#{"Bombay" "Kharagpur" "Madras" "Delhi" "Roorkie" "Guwahati"} city)
;; => "Bombay"

(def subprotocol "sqlite")
;; => #'user/subprotocol
(#{"derby" "h2" "hsqldb" "sqlite"} subprotocol)
;; => "sqlite"

boot-clj/boot

Staying out of trouble

tldr: be careful when a map element’s value can be nil. Also, when some set element can be nil

(def anonymous-book {:title "The Arabian Nights" :author nil})
;; => #'user/anonymous-book
(:author anonymous-book)                
;; => nil
;;although above returns nil, it doesnt imply that :author key is absent
(contains? anonymous-book :author)
;; => true

similar pitfall for sets. Be cautious when the set might contain nil

(def possible-authors #{"Austen" "Dickens" nil})
;; => #'user/possible-authors
(possible-authors nil)
;; => nil
;;although above returns nil, it doesnt mean that my set doesnt contain nil
(contains? possible-authors nil)
;; => true

maps viewed as collection of two element vectors

book
;; => {:title "Oliver Twist", :author "Dickens", :published 1838}
(first book)
;; => [:title "Oliver Twist"]
(rest book)
;; => ([:author "Dickens"] [:published 1838])
(count book)
;; => 3

Logic

The Fundamental if

(defn print-greeting [preferred-customer]
  (if preferred-customer
    (println "Welcome back to Blotts Books!")))
;; => #'user/print-greeting
(print-greeting true)
;; => nil

else

(defn print-greeting [preferred-customer]
  (if preferred-customer
    (println "Welcome back to Blotts Books!")
    (println "Welcome to Blotts Books")))
;; => #'user/print-greeting
(defn shipping-charge [preferred-customer order-amount]
  (if preferred-customer
    0
    (* order-amount 0.10)))
;; => #'user/shipping-charge

Asking Questions

(= 1 1)
;; => true
(= 2 (+ 1 1))
;; => true
(= "Anna Karenina" "Jane Eyre")
;; => false
(= "Emma" "Emma")
;; => true
(= (+ 2 2) 4 (/ 40 10) (* 2 2) (- 5 1))
;; => true
(= 2 3 2)
;; => false
(not= "Anna Karenina" "Jane Eyre")
;; => true
(not= "Anna Karenina" "Anna Karenina")
;; => false

lly < , >, <=, >=

builtin type predicates

(number? 1984)
;; => true
(number? "Anna Karenina")
;; => false
(string? "Anna Karenina")
;; => true
(keyword? "Anna Karenina")
;; => false
(keyword? :anna-karenina)
;; => true
(map? :anna-karenina)
;; => false
(map? {:title 1984})
;; => true
(vector? 1984)
;; => false
(vector? [1984])
;; => true

not, or, and

(defn shipping-surcharge? [preferred-customer express oversized]
  (and (not preferred-customer) (or express oversized)))
;; => #'user/shipping-surcharge?

Truthy and Falsy

Only false and nil are falsy everything else is truthy

Do and When

do is clojure’s version of progn from elisp when = if + do

Dealing with Multiple Conditions

cond The bracketing is reduced in comparision with elisp

(defn shipping-charge [preferred-customer order-amount]
  (cond preferred-customer 0.0
        (< order-amount 50) 5.0
        (< order-amount 100) 10
        (>= order-amount 100) (* order-amount 0.10)))
;; => #'user/shipping-charge

:else

(defn shipping-charge [preferred-customer order-amount]
  (cond preferred-customer 0.0
        (< order-amount 50) 5.0
        (< order-amount 100) 10
        :else (* order-amount 0.10)))
;; => #'user/shipping-charge

case

(defn customer-greeting [status]
  (case status
    :gold "Welcome, golden member!!!"
    :preferred "Welcome back!"
    "Welcome to Blotts Books"))
;; => #'user/customer-greeting

Throwing and Catching

throw, ex-info

(defn publish-book [book]
  (when (not (:title book))
    (throw (ex-info "A book needs a title!" {:book book})))
  ;;...
  )
;; => #'user/publish-book

^ex-info throws an exception of type clojure.lang.ExceptionInfo

try, catch

(try
  (publish-book book)
  (catch ArithmeticException e (println "Math problem."))
  (catch StackOverflowError e (println "Unable to publish..")))
;; => nil
(try
  (publish-book {:author "Dickens"})
  (catch ArithmeticException e (println "Math problem."))
  (catch StackOverflowError e (println "Unable to publish.."))
  (catch clojure.lang.ExceptionInfo e (println e)))
;; => nil

In the wild

Leiningen Korma

Staying Out of trouble

Avoid testing for true explicitly using = Prefer truthy and falsy

More capable Functions

One Function, different parameters

Either one or two arguments

(defn greet
  ([to-whom] (println "Welcome to Blotts Books" to-whom))
  ([message to-whom] (println message to-whom)))
;; => #'user/greet
(greet "Dolly")
(greet "Howdy" "Stranger")

This is called as multi-arity function

How to avoid duplication

(defn greet
  ([to-whom] (greet "Welcome to Blotts Books" to-whom))
  ([message to-whom] (println message to-whom)))
;; => #'user/greet
(greet "Dolly")
(greet "Howdy" "Stranger")

These are called as var-arg or variadic functions

Multimethods

Vary the method call based on some property of data

Books of various formats

(def book1 {:title "War and Peace" :author "Tolstoy"})
;; => #'user/book1
(def book2 {:book "Emma" :by "Austen"})
;; => #'user/book2
(def book3 ["1984" "Orwell"])
;; => #'user/book3

;;normalize book to format {:title ? :author ?}
(defn normalize-book [book]
  (if (vector? book)
    {:title (first book) :author (second book)}
    (if (contains? book :title)
      book
      {:title (:book book) :author (:by book)})))
;; => #'user/normalize-book
(normalize-book book1)
;; => {:title "War and Peace", :author "Tolstoy"}
(normalize-book book2)
;; => {:title "Emma", :author "Austen"}
(normalize-book book3)
;; => {:title "1984", :author "Orwell"}

^above becomes a little messy as we keep adding more book formats

If we had to add a new type of book format, having multimethods will ?enable us to make minimal changes. Look into SICP/data-directed programming as to why this is in detail.

dispatch aka type finder function

(defn dispatch-book-format [book]
  (cond (vector? book) :vector-book
        (contains? book :title) :standard-map
        (contains? book :book) :alternative-map))
;; => #'user/dispatch-book-format

Now we declare a multi method that uses the above dispatch function

(defmulti normalize-book dispatch-book-format)
;; => #'user/normalize-book

implementation of multimethod

(defmethod normalize-book :vector-book [book]
  {:title (first book) :author (second book)})
;; => #multifn[normalize-book 0x6aa71187]
(defmethod normalize-book :standard-map [book]
  book)
;; => #multifn[normalize-book 0x6aa71187]
(defmethod normalize-book :alternative-map [book]
  {:title (:book book) :author (:by book)})
;; => #multifn[normalize-book 0x6aa71187]
(normalize-book book1)
;; => {:title "War and Peace", :author "Tolstoy"}
(normalize-book book2)
;; => {:title "Emma", :author "Austen"}
(normalize-book book3)
;; => {:title "1984", :author "Orwell"}

You can supply a method for :default that will be called if none of the argument’s type matches none of the above.

This is clojure’s way of doing Type-based polymorphism found in OOP languages

Deeply Recursive

(def books [{:title "Jaws" :copies-sold 2000000}
            {:title "Emma" :copies-sold 3000000}
            {:title "2001" :copies-sold 4000000}])
;; => #'user/books
(defn sum-copies
  ([books] (sum-copies books 0))
  ([books total] (if (empty? books)
                   total
                   (sum-copies (rest books) (+ total (:copies-sold (first books)))))))
;; => #'user/sum-copies
(sum-copies books)
;; => 9000000

But this can cause stackoverflow, with as little as 4000 books.

Tail call optimization using recur:-

(defn sum-copies
  ([books] (sum-copies books 0))
  ([books total]
   (if (empty? books)
     total
     (recur (rest books) (+ total (:copies-sold (first books)))))))

All we did is replace the recursive call to sum-copies with recur

To get rid of needing a new arity just for recursive call, we make use of loop

(defn sum-copies [books]
  (loop [books books total 0]
    (if (empty? books)
      total
      (recur (rest books) (+ total (:copies-sold (first books)))))))

Kinda looks like let expression

loop works with recur Whenever a recur is hit, the values passed into recur will be bound to the symbols of the loop, and the loop expression is evaluated again.

recur with or without loop, is the tool that enables us to re-evaluate some block again and again.

Docstrings

Note that the arguments list is written after the docstring

(defn average
  "Return the average of a and b"
  [a b]
  (/ (+ a b) 2.0))
;; => #'user/average

This ordering makes sense:- After all, if the docstring were written after the arguments list, it would be treated as an expression inside the function body.

(doc average)
(def pi "The math constant Pi" 3.14)
;; => #'user/pi
(doc pi)

Clojure provides a way to do the above neatly.

(defn publish-book [book]
  {:pre [(:title book)]}
  (print-book book)
  (ship-book book))

There can be multiple pre conditions

(defn publish-book [book]
  {:pre [(:title book) (:author book)]}
  (print-book book)
  (ship-book book))

There can be post conditions. The % represents ret from the function

(defn publish-book [book]
  {:pre [(:title book) (:author book)]
   :post [(boolean? %)]}
  (print-book book)
  (ship-book book))

If any of the conditions are not met, a runtime exception is thrown.

Staying out of trouble

Always leave a space between & and args when defining variadic functions

In the wild

Implementation of = Use cider repl and M-. to get to this definition. [[file:~/.m2/repository/org/clojure/clojure/1.11.3/clojure-1.11.3.jar:clojure/core.clj::(defn ]] The =m2 folder is where leiningen stores all project dependencies.

Implementation of the builtin to-url in clojurescript file:clojurescript/src/main/clojure/cljs/js_deps.cljc::defmulti to-url class

Functional Things

Functions are first class values in clojure Clojurists regularly write functions that write other functions.

Functions are values

(def dracula {:title "Dracula" :author "Stoker" :price 1.99 :genre :horror})
;; => #'user/dracula
(defn cheap? [book]
  (when (<= (:price book) 9.99)
    book))
;; => #'user/cheap?
(defn pricey? [book]
  (when (> (:price book) 9.99)
    book))
;; => #'user/pricey?
(cheap? dracula)
;; => {:title "Dracula", :author "Stoker", :price 1.99, :genre :horror}
(pricey? dracula)
;; => nil
(defn horror? [book]
  (when (= (:genre book) :horror)
    book))
;; => #'user/horror?
(defn adventure? [book]
  (when (= (:genre book) :adventure)))
;; => #'user/adventure?
(horror? dracula)
;; => {:title "Dracula", :author "Stoker", :price 1.99, :genre :horror}
(adventure? dracula)
;; => nil
(defn cheap-horror? [book]
  (when (and (cheap? book) (horror? book))
    book))
;; => #'user/cheap-horror?
(defn pricy-adventure? [book]
  (when (and (pricey? book) (adventure? book))
    book))
;; => #'user/pricy-adventure?

How can we create such combinations dynamically as need arises:-

(defn both? [first-predicate-f second-predicate-f book]
  (when (and (first-predicate-f book) (second-predicate-f book))
    book))
;; => #'user/both?
(both? cheap? horror? dracula)
;; => {:title "Dracula", :author "Stoker", :price 1.99, :genre :horror}
(both? pricey? adventure? dracula)
;; => nil

Functions on the fly

aka lambda functions

(fn [n] (* 2 n))
;; => #function[user/eval8013/fn--8014]
((fn [n] (* 2 n)) 10)
;; => 20

(defn cheaper-f [max-price]
  (fn [book]
    (when (<= (:price book) max-price)
      book)))
;; => #'user/cheaper-f

cheaper-f is a function that returns another function The returned function remembers the specific max-price that was passed during its creation. Also known as closure.

(def real-cheap? (cheaper-f 1.00))
;; => #'user/real-cheap?
(def kind-of-cheap? (cheaper-f 1.99))
;; => #'user/kind-of-cheap?
(def marginally-cheap? (cheaper-f 5.99))
;; => #'user/marginally-cheap?

(real-cheap? dracula)
;; => nil
(kind-of-cheap? dracula)
;; => {:title "Dracula", :author "Stoker", :price 1.99, :genre :horror}
(marginally-cheap? dracula)
;; => {:title "Dracula", :author "Stoker", :price 1.99, :genre :horror}

A function that manufactures functions such as is sister to both?

(defn both-f [predicate-f-1 predicate-f-2]
  (fn [book]
    (when (and (predicate-f-1 book) (predicate-f-2 book))
      book)))
;; => #'user/both-f
(def cheap-horror? (both-f cheap? horror?))
;; => #'user/cheap-horror?
(cheap-horror? dracula)
;; => {:title "Dracula", :author "Stoker", :price 1.99, :genre :horror}

(def real-cheap-adventure? (both-f real-cheap? adventure?))
;; => #'user/real-cheap-adventure?
(real-cheap-adventure? dracula)
;; => nil

(def real-cheap-horror? (both-f real-cheap? horror?))
;; => #'user/real-cheap-horror?
(def cheap-horror-possession? (both-f cheap-horror?
                                (fn [book] (when (= (:genre book) :possession))
                                  book)))
;; => #'user/cheap-horror-possession?

A functional toolkit

apply

(+ 1 2 3 4)
;; => 10
(apply + [1 2 3 4])
;; => 10

using apply to convert from one type to another

(apply str ["One" 2 3 "Four"])
;; => "One23Four"
(apply list ["One" 2 3 "Four"])
;; => ("One" 2 3 "Four")
(apply vector '("One" 2 3 "Four"))
;; => ["One" 2 3 "Four"]

partial

(inc 42)
;; => 43

(defn my-inc [n] (+ n 1))
;; => #'user/my-inc
(my-inc 42)
;; => 43

(def my-inc2 (partial + 1))
;; => #'user/my-inc2
(my-inc2 42)
;; => 43

notice that partial will bind the first argument of + to 1

Using partial to redefine our cheapness predicates partial eliminates the need for closure creating functions such as cheaper-f But it requires a cheaper-than helper

(defn cheaper-than [max-price book]
  (when (<= (:price book) max-price)
    book))
;;^notice that we want to keep /max-price/ as the first argument, so that partial can handle it

;; => #'user/cheaper-than
(def real-cheap? (partial cheaper-than 1.00))
;; => #'user/real-cheap?
(def kind-of-cheap? (partial cheaper-than 1.99))
;; => #'user/kind-of-cheap?
(def marginally-cheap? (partial cheaper-than 5.99))
;; => #'user/marginally-cheap?

complement

(defn not-adventure? [book]
  (when (not (adventure? book))
    book))
;; => #'user/not-adventure?
(not-adventure? dracula)
;; => {:title "Dracula", :author "Stoker", :price 1.99, :genre :horror}

(defn not-adventure? [book] (not (adventure? book)))
;; => #'user/not-adventure?
(not-adventure? dracula)
;; => {:title "Dracula", :author "Stoker", :price 1.99, :genre :horror}

(def not-adventure? (complement adventure?))
;; => #'user/not-adventure?
(not-adventure? dracula)
;; => true

every-pred is a builtin variadic version of both?

(def cheap-horror? (every-pred cheap? horror?))
;; => #'user/cheap-horror?

(def cheap-horror-possession? (every-pred cheap? horror? (fn [book]
                                                           (when (= (:genre book) :possession)
                                                             book))))
;; => #'user/cheap-horror-possession?

Function literals, also called lambdas

these are even more minimal way of defining functions than fn Its format is just # followed by the function body wrapped in parenthesis

adventure? using function literal

#(when (= (:genre %1) :adventure) %1)

(#(when (= (:genre %1) :adventure) %1) dracula)
;; => nil

double as a function literal

(#(* %1 2) 10)
;; => 20

for only one argument, even more minimal syntax is:

(#(* % 2) 10)
;; => 20

In the wild

Implementation of defn using def and fn ~/.m2/repository/org/clojure/clojure/1.11.3/clojure-1.11.3.jar:clojure/core.clj

update accepts parameters that are functions update-in too

When using Ring, functions as values are extensively used while defining middlewares. Two nice example middlewares are given in the textbook

A web application is not something magical. It is just a function. Traditionally, Ring applications call the final, fully middleware wrapped handler function as the app

Staying out of trouble

Always strive to write pure functions

Let

A local temporary place for your stuff

Book store, say, discounts if final bill is more than a set minimum. If not, bill the minimum amount.

(defn compute-discount-amount [amount discount-percent min-charge]
  (if (> (* amount (- 1.0 discount-percent)) min-charge)
    (* amount (- 1.0 discount-percent))
    min-charge))

Intention revealing naming If we used def, such a binding would be visible even outside the function Because, a def always has global visibility

Also, philosophically, in Clojure, a def is used for binding a global immutable value. Think of it as equivalent to const binding in C++

(defn compute-discount-amount [amount discount-percent min-charge]
  (let [discounted-amount (* amount (- 1.0 discount-percent))]
    (if (> discounted-amount min-charge)
      discounted-amount
      min-charge)))
;; => #'user/compute-discount-amount

In cloujure, let behaves similar to elisp’s let* ie bound values are immediately available

(defn compute-discount-amount [amount discount-percent min-charge]
  (let [discount (* amount discount-percent)
        discounted-amount (* amount discount)]
    (if (> discounted-amount min-charge)
      discounted-amount
      min-charge)))
;; => #'user/compute-discount-amount

Let over Fn

Say, discount is different for each customer

(def user-discounts {"Nicholas" 0.10 "Jonathan" 0.07 "Felicia" 0.05})
;; => #'user/user-discounts

Dumb implementation

(defn compute-discount-amount [amount user-name user-discounts min-charge]
  (let [discount-percent (user-discounts user-name)
        discount (* amount discount-percent)
        discounted-amount (- amount discount)]
    (if (> discounted-amount min-charge)
      discounted-amount
      min-charge)))

^The problem with this approach is that:- compute-discount-amount is not a pure function It is kinda implicit that compute-discount-amount must be evaluated in an environment where user-discounts is properly bound.

It might be better to make use of a closure to make sure that above implicitness is gotten rid of. ie, it ensures the above environment condition

(defn mk-discount-price-f [user-name user-discounts min-charge]
  (let [discount-percent (user-discounts user-name)] ;capture this value as a closure for consumption by the ret function
    (fn [amount]
      (let [discount (* amount discount-percent)
            discounted-amount (- amount discount)]
        (if (> discounted-amount min-charge)
          discounted-amount
          min-charge)))))
;; => #'user/mk-discount-price-f

^This is an example of an Higher-level function (function returning another function) and the outer let and fn together form a closure

Usage

;; Get a price function for Felicia
(def compute-felicia-price (mk-discount-price-f "Felicia" user-discounts 10.0))
;; => #'user/compute-felicia-price

;;...sometime later compute a price when she comes to the store
(compute-felicia-price 20.0)
;; => 19.0

Variations on above theme if-let

if-let

(def anonymous-book {:title "Sir Gawain and the Green Knight"})
;; => #'user/anonymous-book
(def with-author {:title "Once and Future King" :author "White"})
;; => #'user/with-author

(defn uppercase-author [book]
  (let [author (:author book)]
    (if author
      (.toUpperCase author))))
;; => #'user/uppercase-author
(uppercase-author with-author)
;; => "WHITE"
(uppercase-author anonymous-book)
;; => nil

(defn uppercase-author [book]
  (if-let [author (:author book)]
    (.toUpperCase author)
    "ANONYMOUS"))
;; => #'user/uppercase-author
(uppercase-author with-author)
;; => "WHITE"
(uppercase-author anonymous-book)
;; => "ANONYMOUS"

if-let is a misnomer, it should have been called let-if

There is a when-let builtin as well

In the wild

In Ring:- (defn parse-params (defn assoc-query-params

Incanter

Staying out of trouble

let follows lexical scope. Lexical scope means that bindings created by let have a scope limited by the body of the let expression. Unlike elisp.

Def, Symbols, and Vars

A Global, Stable place for your stuff

def can be used to define global constants Global constants are capitalized by convention

Symbols are Things

def binds a symbol to a value

(def author "Austen")

The above involes one symbol and one value Both of them are first-class and take up bytes in memory

The symbol is 'author The value is "Austen"

Symbols have a lot in common with keywords.

Bindings are Things too

When you evaluate a def or defn, aka create a binding, Clojure creates a var

(def author "Austen")
;; => #'user/author
(def title "Emma")
;; => #'user/title
(def PI 3.14)
;; => #'user/PI
(def book1 {:title title :author author})
;; => #'user/book1
(defn book-description [book] (str (:title book) " written by " (:author book)))
;; => #'user/book-description

What these^ var looks like:- var has two slots

Sharp-quote can be used to get a var that created some symbol

;;makes a var:-
(def author "Austen")
;; => #'user/author

;;Get above created var:-
#'author                                ;?Doubt: is this syntactic sugar for (var author)
;; => #'user/author

(def the-var #'author)
;; => #'user/the-var

(.get the-var)
;; => "Austen"
(.-sym the-var)
;; => author

Varying Your Vars

Vars are mutable in Clojure. This is helpful during development. But, in production, respect the convention of not changing a var once its defined. Aka, never change a once defined def and defn

dynamic-var

;;Make a dynmic-var
(def ^:dynamic *debug-enabled* false)

(defn debug [msg]
  (if *debug-enabled*
    (println msg)))

(binding [*debug-enabled* true]
  (debug "Calling that bug prone function:-")
  (some-troublesome-function-that-needs-logging) ;;for this call and all call stacks below it, the dynamic var setting remains ~true~
  (debug "Back from that bug prone function."))

binding :- Helps you change a symbol without resorting to the BAD anti-pattern of using def inside a defn symbols vars intended to be used in binding are called dynamic vars

By convention, they are sorrounded by * They are called as sorrounded by earmuffs

Staying out of trouble

Note:- let does not create var

(let [let-bound-symbol 42]
  #'let-bound-symbol)
;; => Syntax error compiling var at (readme.org::Def, Symbols, and Vars:67:1).
;;    Unable to resolve var: let-bound-symbol in this context

SICP taught me that let is just syntactic sugar for a lambda definition and that lambda being called

In the wild

clojure/core.clj file clojure/core_print.clj file

set! changes the value of a dynamic-var ‘from inside the binding’

*1, *2 and *3 are dynamic vars with a single earmuff They represent the last ret, last to last ret and the third last ret respectively

*e represents the last exception

Wrapping up

def creates a var(a Clojure value) which is an association between another Clojure value (a symbol) and a third value

Dynamic vars are vars that let you swap in a new value while you evaluate an expressin or six

Namespaces

Namespace is the mechanism that Clojure uses to organize vars into related buckets.

A place for your vars

vars live in namespaces

Conceptually, a namespace is just a big lookup table of vars, indexed by their symbols You can visualise a namespace as follows:- xournalpp:namespace visualisation.xopp

Auto created default namespace is named as ‘user’ However, if lein repl is run inside a project, the default namespace is named as ‘<project_name>.core’

Create a new namespace

(ns pricing)
;; => nil

Notice no quotation of necessary for argument

Above creates and activates that namespace

(def discount-rate 0.15)
;; => #'pricing/discount-rate
(defn discount-price [book]
  (* (- 1.0 discount-rate) (:price book)))
;; => #'pricing/discount-price

Switch back to an existing namespace is also done using ns

(ns user)
;; => nil
discount-rate
;; => Syntax error compiling at (Getting_Clojure.org::Namespaces:0:0).
;;    Unable to resolve symbol: discount-rate in this context
(ns pricing)
;; => nil
discount-rate
;; => 0.15

Fully qualified symbol = <ns>/<symbol name> Such long name makes a symbol defined in one namespace available in another

(ns user)
;; => nil
(pricing/discount-price {:title "Emma" :price 9.99})
;; => 8.4915

Loading Namespaces using require

You need to make sure the namespace you want to use is loaded before you try to use it. (require <quoted name>) or (require '[<name> :as blabla])

(Aside: We will learn another way to loading namespaces later in this chapter using (ns <bla> (:require <unquoted>)))

Eg: clojure.data is a builtin namespace. (The dot is part of the name of this namespace) Say, I need to use diff which is defined in that namespace.

(def literature ["Emma" "Oliver Twist" "Possession"])
;; => #'user/literature
(def horror ["It" "Carry" "Possession"])
;; => #'user/horror

(clojure.data/diff literature horror)
;; => Execution error (ClassNotFoundException) at java.net.URLClassLoader/findClass (URLClassLoader.java:445).
;;    clojure.data

^Because, namespace not yet loaded

Tell Clojure to read and compile the code behind some namespace:-

(require 'clojure.data)
;; => nil
(clojure.data/diff literature horror)
;; => [["Emma" "Oliver Twist"] ["It" "Carry"] [nil nil "Possession"]]

A namespace of your own

(From here till ‘In the wild’ of this chapter, its more convenient to work inside a lein repl)

blottsbooks

Create a file src/blottsbooks/pricing.clj This corresponds to the namespace:- (ns blottsbooks.pricing)

(:require + As) and Refer

(ns blottsbooks.core
  (:require [blottsbooks.pricing :as pricing])
  (:gen-class))

[anup@imac0 blottsbooks]$ lein repl

blottsbooks.core=> pricing/discount-rate 0.15 blottsbooks.core=> pricing/discount-price #object[blottsbooks.pricing$discount_price 0x6d7c552 “blottsbooks.pricing$discount_price@6d7c552”]

^Visualise as:- xournalpp:require-visualised.xopp

Avoid using :refer and mostly prefer to using :as

Namespaces, Symbols and Keywords

Q: Are namespaces first class citizens in Clojure? Yes! Namespaces are just ordinary Clojure values Eg:- The current namespace is bound to the symbol *ns* (Recall that earmuffs represent dynamic vars)

(println "Current ns:" *ns*)

Look up a namespace. Useful to check the existence of some namespace.

(find-ns 'user)
;; => #namespace[user]
(find-ns 'doesnt-exist)
;; => nil

Map the things defined in some namespace

(ns-map 'user)
;; => {primitives-classnames #'clojure.core/primitives-classnames,
;;     +' #'clojure.core/+',
;;     Enum java.lang.Enum,
;;     decimal? #'clojure.core/decimal?,
;;     restart-agent #'clojure.core/restart-agent,
;;     sort-by #'clojure.core/sort-by,
;;     ...,
;;     refer-clojure #'clojure.core/refer-clojure}

(ns-map 'doesnt-exist)
;; => Execution error at user/eval10058 (REPL:118).
;;    No namespace: doesnt-exist found

(namespace 'some-namespace/some-symbol)
;; => "some-namespace"

^(cant be used to check the existence of some namespace without writing code to catch exceptions etc… so prefer find-ns for such check)

Keywords can be namespaced too, but are seldom done.

In the wild

clojure.core :- (require '[clojure.core :refer :all])


How to include a dependency into your project:- Eg: Say you want to include the KormaSQL library

Go to that library’s homepage or github and figure out the latest version Github readme says [korma "0.4.3"]

Include this array into projects.clj file created by leiningen Next time you run lein repl, that dependency gets downloaded and included

Now, how to figure out what argument to pass to 'require Looking at the example from the readme, I see that the first line is (use 'korma.db) From this, I get my argument korma.db aka [korma.db :as db]

[anup@imac0 blottsbooks]$ lein repl Retrieving korma/korma/0.4.3/korma-0.4.3.pom from clojars Retrieving org/clojure/clojure/1.8.0/clojure-1.8.0.pom from central Retrieving com/mchange/c3p0/0.9.5.2/c3p0-0.9.5.2.pom from central Retrieving com/mchange/mchange-commons-java/0.2.11/mchange-commons-java-0.2.11.pom from central Retrieving org/clojure/java.jdbc/0.6.1/java.jdbc-0.6.1.pom from central Retrieving org/clojure/clojure/1.4.0/clojure-1.4.0.pom from central Retrieving org/sonatype/oss/oss-parent/5/oss-parent-5.pom from central Retrieving com/mchange/c3p0/0.9.5.2/c3p0-0.9.5.2.jar from central Retrieving com/mchange/mchange-commons-java/0.2.11/mchange-commons-java-0.2.11.jar from central Retrieving org/clojure/java.jdbc/0.6.1/java.jdbc-0.6.1.jar from central Retrieving korma/korma/0.4.3/korma-0.4.3.jar from clojars nREPL server started on port 37507 on host 127.0.0.1 - nrepl://127.0.0.1:37507 REPL-y 0.5.1, nREPL 1.0.0 Clojure 1.11.1 OpenJDK 64-Bit Server VM 21.0.4+7-LTS Docs: (doc function-name-here) (find-doc “part-of-name-here”) Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e

blottsbooks.core=> (require ‘[korma.db :as db]) nil blottsbooks.core=> (count (ns-map ‘korma.db)) 796 blottsbooks.core=>

Staying out of trouble

The . in a namespace doesnt have any special meaning to Clojure.

Pass :reload keyword to re-evaluate a namespace. By default, already included namespace isnt evaluated, even if the source code has new edits.

defonce can be used to bind a symbol to a value, with the special property that this binding happens exactly once. The very first time you include that namespace. Subsequent :reload refuses to re-run those bindings. This is helpful when you have function with side effects or those that take a long time to finish.

ns-unmap can be used to unbind a ?symbol

This marks the end of Part I - Basics ------------------------------------------------------------- Next starts Part II - Intermediate

Part II ---------------------------------------------------------------------------------------------------

Sequences

Clojure code isnt written with specificity to each collection type: map, set, vector, list It is written to a sequence_abstract_type0

One thing after another

How can we implement a count that works on all collection types:- Option1. multimethod + specific defmethod implementation for each type Option2. normal function that works on a seq + collection type turned into a seq by making use of a wrapper_function(<specific_type>) = <seq_type> Clojure chooses Option 2 (This is known as Adapter-Pattern in OOP circles)

Clojure calls its generic collection wrappers [as] sequences. Under the hood, there are as mnay flavors of sequences as there are collection types, but to the outside world all sequences provide a very uniform interface: no matter if it’s a vector or a map or a list or a set behind a sequence, one sequence looks exactly like another.

What a seq looks like

(def book-title-seq (seq ["Emma" "Oliver Twist" "Robinson Crusoe"]))
;; => #'user/book-title-seq
book-title-seq
;; => ("Emma" "Oliver Twist" "Robinson Crusoe")

Dont be fooled by the round parenthesis into thinking its a list type Its a seq

(seq '("Emma" "Oliver Twist" "Robinson Crusoe"))
;; => ("Emma" "Oliver Twist" "Robinson Crusoe")
(seq {:title "Emma", :author "Austen", :published 1815})
;; => ([:title "Emma"] [:author "Austen"] [:published 1815])
;; ^order not guranteed

(seq (seq [1 2 3]))
;; => (1 2 3)

seq returns nil if empty

(seq [])
;; => nil
(seq '())
;; => nil
(seq '{})
;; => nil

^ (seq <some collection) can therefore be used as a truthy value

A Universal interface - with a foursome of functions

  1. first
  2. rest
  3. next
  4. cons

From hereon, this chapter feels a lot like Ch-1 of SICP

next unlike rest, returns nil if empty

(next [1])
;; => nil
(rest [1])
;; => ()

Lets get a feel for these four functions

(first (seq '("Emma" "Oliver Twist" "Robinson Crusoe")))
;; => "Emma"
(rest (seq '("Emma" "Oliver Twist" "Robinson Crusoe")))
;; => ("Oliver Twist" "Robinson Crusoe")
(next (seq '("Emma" "Oliver Twist" "Robinson Crusoe")))
;; => ("Oliver Twist" "Robinson Crusoe")
(cons "Mahabharata" '("Emma" "Oliver Twist" "Robinson Crusoe"))
;; => ("Mahabharata" "Emma" "Oliver Twist" "Robinson Crusoe")

And, that’s it. These are the only four primitive functions necessary to define a huge number of sequence functions

Lets try to implement my-count

;;my own try:-
(defn my-count [col]                    ;col stands for collection, aka, any collection type
  (loop [col-s (seq col)
         ret 0]
    (if (next col-s)
      (recur (next col-s) (+ ret 1))
      (+ ret 1))))
;; => #'user/my-count
(my-count [9 3 5])
;; => 3
(my-count [nil nil nil])
;; => 3

;;textbook:-
(defn my-count [col]
  (let [the-seq (seq col)]
    (loop [ret 0 s the-seq]
      (if (seq s)                       ;to take care of turning the final [] into nil
        (recur (inc ret) (rest s))
        ret))))
(my-count [9 3 5])
;; => 3
(my-count [nil nil nil])
;; => 3

^Anup self tip: Prefer to use rest over next Because rest always returns a seq-type. So, results in cleaner code type wise. Unlike next which can return either a seq-type or nil

my-count implementation follows a pattern,

  1. turn collection into a sequence
  2. use the foursome to manipulate this seq

This is the common pattern in which even the builtin seq functions are implemented

A Rich Toolkit - the large number of useful builtin seq functions provided by Clojure

(count [9 2 5])
;; => 3
(def titles ["Jaws" "Emma" "2001" "Dracula"])
;; => #'user/titles
(reverse titles)
;; => ("Dracula" "2001" "Emma" "Jaws")
(sort titles)
;; => ("2001" "Dracula" "Emma" "Jaws")
(reverse (sort titles))
;; => ("Jaws" "Emma" "Dracula" "2001")
(def titles-and-authors ["Emma" "Austen" "Jaws" "Benchley"])
;; => #'user/titles-and-authors
(partition 2 titles-and-authors)
;; => (("Emma" "Austen") ("Jaws" "Benchley"))

(def titles ["HackersPainters" "Emma"])
;; => #'user/titles
(def authors '("PGraham" "Austen"))
;; => #'user/authors
(interleave titles authors)
;; => ("HackersPainters" "PGraham" "Emma" "Austen")
;;;;;;;; interleave combines two sequences into a single sequence. So, kinda like ~append~ in scheme

(def scary-animals ["lion" "tiger" "crocodile"])
;; => #'user/scary-animals
(interpose "and" scary-animals)
;; => ("lion" "and" "tiger" "and" "crocodile")

…Made Richer with Functional Values

ie adding the awesome ‘functions are values’ to above ‘sequence abstract type’ idea makes for a beautiful experience

(filter neg? '(1 9 -2 4 -8 5 -23))
;; => (-2 -8 -23)

(def books [{:title "Deep Six" :price 13.99 :genre :sci-fi :rating 6}
            {:title "Dracula" :price 1.99 :genre :horror :rating 7}
            {:title "Emma" :price 7.99 :genre :comedy :rating 9}
            {:title "2001" :price 10.50 :genre :sci-fi :rating 5}])
;; => #'user/books
(defn cheap? [book]
  (if (<= (:price book) 9.99)
    book))
;; => #'user/cheap?
(filter cheap? books)
;; => ({:title "Dracula", :price 1.99, :genre :horror, :rating 7}
;;     {:title "Emma", :price 7.99, :genre :comedy, :rating 9})

(some cheap? books)
;; => {:title "Dracula", :price 1.99, :genre :horror, :rating 7}
;; Mnemonic:- Is there some item that passes the test?

Map

(map inc [0 5 9])
;; => (1 6 10)

books
;; => [{:title "Deep Six", :price 13.99, :genre :sci-fi, :rating 6}
;;     {:title "Dracula", :price 1.99, :genre :horror, :rating 7}
;;     {:title "Emma", :price 7.99, :genre :comedy, :rating 9}
;;     {:title "2001", :price 10.5, :genre :sci-fi, :rating 5}]

(map :title books)
;; => ("Deep Six" "Dracula" "Emma" "2001")
;; This trick with keywords being functions makes code succinct

;; Lengths of the titles
(map (comp count :title) books)
;; => (8 7 4 4)

comp accepts a bunch of functions and returns a function that is effectively the same as applying each of the argument functions one after another, right to left I was not able to recall the name of this function. I found that ChatGPT was very useful in finding the answer. Whereas google search couldnt help me find it.

for is not popular in Clojure circles. Still, obligatory example:-

(for [b books]
  (count (:title b)))
;; => (8 7 4 4)

Clojure’s for returns a seq.

Reduce

Combines all the elements of a collection into a single value

(reduce + 100 [1 2 3 4])
;; => 110
(reduce + [1 2 3 4])
;; => 10

If you exclude the initial value, then reduce will consider the first element of the collection as the initial value.

Reduce is not only for adding numbers It can be used whenever you want to turn a sequence into some single value.

Eg:- Find the highest priced book (my own version)

books
;; => [{:title "Deep Six", :price 13.99, :genre :sci-fi, :rating 6}
;;     {:title "Dracula", :price 1.99, :genre :horror, :rating 7}
;;     {:title "Emma", :price 7.99, :genre :comedy, :rating 9}
;;     {:title "2001", :price 10.5, :genre :sci-fi, :rating 5}]
(reduce (fn [b1 b2] (if (> (:price b1) (:price b2)) b1 b2)) books)

textbook version has a mistake: It finds the price of the highest priced book. Not the highest priced book as described in question.

(defn hi-price [current-highest-price book]
  (if (> (:price book) current-highest-price)
    (:price book)
    current-highest-price))
;; => #'user/hi-price

(reduce hi-price 0 books)
;; => 13.99

Composing a Solution

Get the top 3 bestrated books as a string seperated by ” // ”

books
;; => [{:title "Deep Six", :price 13.99, :genre :sci-fi, :rating 6}
;;     {:title "Dracula", :price 1.99, :genre :horror, :rating 7}
;;     {:title "Emma", :price 7.99, :genre :comedy, :rating 9}
;;     {:title "2001", :price 10.5, :genre :sci-fi, :rating 5}]

(sort-by :rating books)
;; => ({:title "2001", :price 10.5, :genre :sci-fi, :rating 5}
;;     {:title "Deep Six", :price 13.99, :genre :sci-fi, :rating 6}
;;     {:title "Dracula", :price 1.99, :genre :horror, :rating 7}
;;     {:title "Emma", :price 7.99, :genre :comedy, :rating 9})

(defn format-top-titles [books]
  (reduce str (interpose " // " (map :title (take 3 (reverse (sort-by :rating books)))))))
;; => #'user/format-top-titles

(format-top-titles books)
;; => "Emma // Dracula // Deep Six"

You can get a lot of computing out of a few sequence functions. Remember that even SICP taught me the same.

Other Sources of Sequences

You can turn a lot of things into sequences, besides vectors, lists, sets, maps

line-seq turns a text file into a sequence of its lines

(require '[clojure.java.io :as io])
;; => nil
(defn listed-author? [author]
  (with-open  [r (io/reader "authors.txt")]
    (some (partial = author) (line-seq r))))
;; => #'user/listed-author?
(listed-author? "Paul Graham")
;; => true

re-seq turns regular expression matches into a sequence

(def re #"Pride and Prejudice.*")
;; => #'user/re
(def title "Pride and Prejudice and Zombies")
;; => #'user/title
(if (re-matches re title)
  (println "This is either a classic or a riff on it"))
;; => nil

(re-seq #"\w+" title)
;; => ("Pride" "and" "Prejudice" "and" "Zombies")

Notice that #( is used for function literals whereas #" is used for regular expression

In the wild

Overtone Clojurescript

->> syntactic sugar places each step of computation at the end ie it becomes the last argument

books
;; => [{:title "Deep Six", :price 13.99, :genre :sci-fi, :rating 6}
;;     {:title "Dracula", :price 1.99, :genre :horror, :rating 7}
;;     {:title "Emma", :price 7.99, :genre :comedy, :rating 9}
;;     {:title "2001", :price 10.5, :genre :sci-fi, :rating 5}]

;;harder to read
(defn format-top-titles [books]
  (reduce str (interpose " // " (map :title (take 3 (reverse (sort-by :rating books)))))))
;; => #'user/format-top-titles
(format-top-titles books)
;; => "Emma // Dracula // Deep Six"

;;easier to read
(defn format-top-titles [books]
  (->>
    books
    (sort-by :rating)
    reverse                             ;;no need to write it as ~(reverse)~
    (take 3)
    (map :title)
    (interpose " // ")
    (reduce str)))
;; => #'user/format-top-titles
(format-top-titles books)
;; => "Emma // Dracula // Deep Six"

-> syntactic sugar places it (each step of computation) at the front of the argument list. ie, it becomes the first argument. Aside, for nested properties, there is a sister syntactic sugar, double dot, Eg:- (.. event -target -value)

Staying out of trouble

Avoid processing sequence items one at a time. Its a Clojure antipattern. Eg: for is seldom used by Clojurists, they prefer map

Turning a specialized collection into a generic sequence leads to loss of its specialized talents. Eg: After turning a_map into a a_seq, the fast key-value access property is lost.

There are functions that do not return a seq type, instead returning the same type of collection as its argument. Eg: conj, (unlike cons) Such function’s implementation has special code for each collection type, unlike seq-functions which only make use of the foursome.

None of this means sequences are bad or that you should always fight the natural drift towards them. On the contrary, having a universal abstraction that allows you to work with vectors and sets and lists without constantly worrying about which you have is incredibly useful. But you do need to be mindful of when you have the actual thing and when you have the sequence.

Lazy Sequences

made possible by combining previous ideas:-

  1. Functional programming
  2. Sequence abstraction

Laziness is a virtue. Aside: AirBnB founder said “You have to earn the right to ship a new feature for your user.”

Aside:- Doubt? Where is Laziness on the spectrum between: 1. Optimize time complexity while sacrificing space complexity and 2. vice versa. Ans try1: It is not one or the other. Laziness enables us to represent infinity. (apart from allowing use to defer computation) Both infinitely large dataset as well as infinitely long computation

Sequences without End

The sequence api is abstract. As long as it satisfies foursome, its a seq. That’s it.

So, even if we make up return values of first, next and rest on the fly, we still have a seq. ie It isnt necessary for a seq to have a one on one mapping to some collection type.

Create a test book with nonsense content. The nonsense is a repetition of a proverb.

;;the proverb
(def jack "All work and no play makes Jack a dull boy.")
;; => #'user/jack

;;dummy textbook
(def text [jack jack jack jack jack jack])
;; => #'user/text
text
;; => ["All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."]

(set! *print-length* 20)
;; => 20

(repeat jack)
;; => ("All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy."
;;     ...)

(def text (repeat jack))
;; => #'user/text

The ret from repeat is infinitely long. Also, its lazy, ie, it waits until its asked to generate a value by those foursome.

A Lazy sequence is one that waits to be asked before it generates its elements. An Unbounded sequence is always a lazy sequence, that goes on forever.

Not all lazy sequences are unbounded.

take is useful while dealing with unbounded sequences.

(take 2 text)
;; => ("All work and no play makes Jack a dull boy."
;;     "All work and no play makes Jack a dull boy.")

More interesting laziness

cycle and iterate

(cycle [1 2 3])
;; => (1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 ...)
(iterate inc 4)
;; => (4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ...)
(def numbers (iterate inc 1))
;; => #'user/numbers

Lazy friends

take is itself lazy.

Therefore, both these take approximately same amount of time.

(println (take 20 (iterate inc 1)))
;; => nil
(println (take 20 (take 1000000000 (iterate inc 1))))
;; => nil

map is also lazy.

(def evens (map #(* 2 %) (iterate inc 1)))

iterleave is lazy as well. So, we can safely interleave infinite sequences.

(interleave numbers evens)
;; => (1 2 2 4 3 6 4 8 5 10 6 12 7 14 8 16 9 18 10 20 ...)

In fact, a lot of sequence functions are lazy

Visualization for lazy sequences Think of it as a just in time factory line ie Production kicks off only after you ask for an element from it.

Laziness in practise - an example

Eg: Generate a book series, with twenty authors. Every twentieth book of the series is by one of the authors.

What a series looks like:-

(def numbers [1 2 3])
;; => #'user/numbers
(def trilogy (map #(str "Fifty shades of gray, Book " %) numbers))
;; => #'user/trilogy
trilogy
;; => ("Fifty shades of gray, Book 1"
;;     "Fifty shades of gray, Book 2"
;;     "Fifty shades of gray, Book 3")

There are 20 authors that write the books in the series. After finising the first book, the first author writes the 21st book in the series, and so on.

Lets make the book titles

(def numbers (iterate inc 1))
;; => #'user/numbers
(def titles (map #(str "Fifty shades of gray, Book " %) numbers))
;; => #'user/titles

Lets make the authors

(def first-names ["Bob" "Jane" "Chuck" "Leo"])
;; => #'user/first-names
(def last-names ["Jordan" "Austen" "Dickens" "Tolstoy" "Poe"])
;; => #'user/last-names

;; There will be (* 4 5) = 20 authors
(defn combine-names [fname lname] (str fname " " lname))
;; => #'user/combine-names

(def authors (map combine-names (cycle first-names) (cycle last-names)))
;; => #'user/authors

Make the map of books

(defn make-book [title author] {:title title :author author})
;; => #'user/make-book
(def test-books (map make-book titles authors))
;; => #'user/test-books

Whenever (first test-books) is called, it triggers a cascade of computation. But, the key is that we only compute just-in-time. Hence, we only pay for both the CPU and Memory that we actually use.

Behind the Scenes

how to create a lazy sequence

(lazy-seq [1 2 3])
;; => (1 2 3)

lazy-seq vs seq

(defn chatty-vector []
  (println "I return a chatty vector")
  [1 2 3])
;; => #'user/chatty-vector

(def s1 (seq (chatty-vector)))          ;prints

(def s2 (lazy-seq (chatty-vector)))
(first s2)                              ;now prints

In Chapter 20, we will learn that lazy-seq is implemented using some macro magic.


How lazy sequence creation operations internally work:-

My own version of repeat, iterate, map

(defn my-repeat [x]
  (cons x (lazy-seq (my-repeat x))))
;; => #'user/my-repeat

(defn my-iterate [f x]
  (cons (f x) (lazy-seq (my-iterate f (f x)))))
;; => #'user/my-iterate

my-map will also teach me how to terminate when one of the args is not infinite. Its implementation also uses the idea that you can cons onto nil

(cons 3.14 nil)
;; => (3.14)

;;;;below wont work for finite col
;;(defn my-map [f col]
;;  (cons (f (first col))
;;    (lazy-seq (my-map f (next col)))))

(defn my-map [f col]
  (when col
    (cons (f (first col))
      (lazy-seq (my-map f (next col))))))
;; => #'user/my-map

Staying Out of Trouble

Helpful to avoid printing infintely long seqs

(set! *print-length* 20)

Aside:- slurp :- Universal Clojure tool to read something. Accepts url, filepath etc spit :- Write string to file path

(slurp "authors.txt")
(spit "/tmp/spit-out.txt" "Abrakadabra")

Be careful about side-effects when dealing with lazy sequences. Take into consideration the state when the lazy sequence is finally evaluated, not at creation time of that sequence.

Use doall to force evaluation of a lazy seq right NOW. doseq is similar, and useful when you dont-want/space-wont-permit to hold a reference to the entire unlazified-sequence

In the wild

nREPL ^Show a nice Clojure design pattern0 to turn a side-effect driven event into a lazy-seq Further processing is now easy. Because Clojure has a rich library of seq builtins.

?Visualize above design pattern:- xournalpp:model a stream of side effects as a lazy seq.xopp

Lazy sequences are very very common in Clojure. Only a few exceptions are not-lazy aka eager:- count

Destructuring

aka How to pluck just the value we need from some collection.

Pry Open your Data

(def artists [:monet :austen])
;; => #'user/artists
(let [[painter novelist] artists]
  (println (str "The painter is: " painter))
  (println (str "The novelist is: " novelist)))

Getting Less than Everything

(def artists [:monet :austen :beethoven :kuvempu])
;; => #'user/artists
(let [[painter novelist musician] artists]
  (println (str painter " // " novelist " // " musician)))

Ignoring some leading values

(let [[_ _ musician poet] artists]
  (println (str musician " // " poet)))
;;There is nothing special about '_', its just a convention
;;As usual, _ gets bound to :monet, then gets rebound to :austen, which is its value in the let body, but is unused

Two-level vector’s destructuring

(def pairs [[:monet :austen] [:beethoven :dickinson]])
;; => #'user/pairs
(let [[[painter] [musician _]] pairs]   ;;The first braket need not compulsorily be [painter _]
  (println (str painter " // " musician)))

Destructuring in Sequence

Same syntax as vector destructuring, continue to use square brackets around the capturing template

(def artist-list '(:monet :austen :beethoven :dickinson))
;; => #'user/artist-list
(let [[painter novelist musician] artist-list]
  (println (str painter " // " novelist " // " musician)))

Those square brackets stand for delimiters for ANY sequential data type. (but we use flower bracket for map destructuring template)

Eg: String is a seq type. Therefore its characters can be destructured:-

(let [[c1 c2 c3 c4] "Anup"]
  (println (str "My name is spelled as:"))
  (println c1)
  (println c2)
  (println c3)
  (println c4))

The rule is, if you can turn it into a sequence, you can destructure it.

Destructuring Function Arguments

(defn artist-description [[novelist poet]]
  (str "The novelist is: " novelist " and the poet is: " poet))
;; => #'user/artist-description
(artist-description [:austen :kuvempu])
;; => "The novelist is: :austen and the poet is: :kuvempu"

Just keep in mind that the value to be destructured is provided when the function is called

Mix and match normal function arguments with destructured arguments

(defn artist-description [shout [novelist poet]]
  (let [msg (str "The novelist is: " novelist " and the poet is: " poet)]
    (if shout
      (.toUpperCase msg)
      msg)))
;; => #'user/artist-description
(artist-description true [:austen :kuvempu])
;; => "THE NOVELIST IS: :AUSTEN AND THE POET IS: :KUVEMPU"

Digging into Maps

The ordering in the soaking-template is counter-intuitive. Instead of key, then value_soaker The ordering is value_soaker, then key

(def artist-map {:painter :monet :novelist :austen})
;; => #'user/artist-map
(let [{painter :painter novelist :novelist} artist-map]
  (println (str painter " // " novelist)))
;; => nil

Doubt: We know that SICP taught me that let is just syntactic sugar for a lambda definition and that lambda being called Can this explain the counter intuitive ordering?

Diving into Nested Maps

(def austen {:name "Jane Austen" :parents {:father "George" :mother "Cassandra"} :dates {:born 1775 :died 1817}})
;; => #'user/austen
(let [{{father :father mother :mother} :parents} austen]
  (str "Her father was " father " and mother was " mother))
;; => "Her father was George and mother was Cassandra"

(let [{name :name
       {mother :mother} :parents
       {dob :born} :dates} austen]
  (str name " was born in " dob " and her mother's name was " mother))
;; => "Jane Austen was born in 1775 and her mother's name was Cassandra"

The final frontier: Mixing and Matching

Eg1

(def author {:name "Jane Austen"
             :books [{:title "Sense and Sensibility" :published 1811}
                     {:title "Emma" :published 1815}]})
;; => #'user/author
(let [{name :name
       [book] :books} author]
  (str "The author is " name " and one of their books is " book))
;; => "The author is Jane Austen and one of their books is {:title \"Sense and Sensibility\", :published 1811}"

Eg2

(def authors [{:name "Jane Austen" :born 1775}
              {:name "Charles Dickens" :born 1812}])
;; => #'user/authors
(let [[{dob1 :born} {dob2 :born}] authors]
  (str "One author was born in " dob1 " and the other author was born in " dob2))
;; => "One author was born in 1775 and the other author was born in 1812"

Going further

In this section we learn two more tricks:-

  1. How to automatically make the key_name as the capturing name
    (def character {:name "Mario" :age 16 :gender :male})
    ;; => #'user/character
    
    (defn character-desc [{name :name age :age gender :gender}]
      (str "Name: " name " Age: " age " Gender: " gender))
    ;; => #'user/character-desc
    
    (defn character-desc [{:keys [name age gender]}]
      (str "Name: " name " Age: " age " Gender: " gender))
    ;; => #'user/character-desc
    
    (character-desc character)
    ;; => "Name: Mario Age: 16 Gender: :male"
        

Notice that ordering of :keys and [name age gender] is counter-intuitive. This reversal, ie, a keyword on the left side, lets the destructuring mechanism to treat this as a special case.

Just remember the capture template syntax :keys [keyword_without_left_colon_1 keyword_without_left_colon_2 ...]


Mix and match normal destructuring with :keys based destructuring

(defn character-desc [{:keys [name gender]
                       age-in-years :age}]
  (str "Name: " name " age:" age-in-years " gender: " gender))
;; => #'user/character-desc
(character-desc character)
;; => "Name: Mario age:16 gender: :male"

  1. How to capture the both the un-destructured whole along with some destructured from it.

Lets say we want to add a new key called :greeting into the above character of map type

To bulid the ret by consing assoc-ing, we need the original map But to build the value of greeting, we need the destructured name, age, gender

So, we need to capture both

(defn add-greeting [{:keys [name age] :as character}]
  (assoc character
    :greeting (str "Hello, my name is " name " and I am " age " years old.")))
;; => #'user/add-greeting
(add-greeting character)
;; => {:name "Mario",
;;     :age 16,
;;     :gender :male,
;;     :greeting "Hello, my name is Mario and I am 16 years old."}

Just remember the syntax :as character within the flower brackets

xournalpp:double-dot-as.xopp

Staying out of trouble

Whenever you want to dive very deep:-

  1. Drill down in multiple stages, with descriptive intermediate names (intention-revealing names)

Eg: Lets say we want to print the fullname of the second user’s favorite author:-

(def user-info [{:name "Charlie", :fav-book {:title "Carrie", :author ["Stephen" "King"]}}
                {:name "Jennifer", :fav-book {:title "Emma", :author ["Jane" "Austen"]}}])
;; => #'user/user-info

;;BAD VERSION
;;full name of the author of the second reader's fav book
;;(defn format-a-name [[_
;;                      {{[first-name last-name] :author} :fav-book}]]
;;  (str first-name " " last-name))
;; => #'user/format-a-name

(format-a-name user-info)
;; => "Jane Austen"

;;GOOD VERSION
(defn format-a-name [[_ second-user]]
  (let [[first-name last-name] (->> second-user :fav-book :author)]
    (str first-name " " last-name)))
;; => #'user/format-a-name

(format-a-name user-info)
;; => "Jane Austen"

  1. Destructuring only works inside a let or inside a defn
(def author {:name "Jane Austen" :born 1775})
;; => #'user/author

;;WONT WORK
;;(def author-name [{n :name} author])

ie, we cannot use the destructuring template followed by ‘author’ outside of the above two forms

A let can be used to make above work

(def author-name (let [{name :name} author] name))
;; => #'user/author-name
author-name
;; => "Jane Austen"

In the wild

Korma SQL library ^We also learn :or which is a sister of :as :or lets you set default values when destructuring fails due to absence of some key.

Wrapping up

Destructuring is soo much better than first-ing , rest-ing, nth-ing till your head explodes 😀

Records and Protocols

Records are like maps, but specialized for a particular purpose.

Two troubles with Maps

  1. any key with any value, this flexibility => slow code
  2. lack of coherence and documentation, Eg: A map name watson could be either of a fictional character or a supercomputer.

How to create a Record

(defrecord FictionalCharacter [name appears-in author])
;; => user.FictionalCharacter

^This creates a record and two functions that help create instances:- ->FictionalCharacter & map->FictionalCharacter

(def watson (->FictionalCharacter "John Watson" "Sherlock Holmes" "Doyle"))
;; => #'user/watson

Or, create from a map

(def elizebeth (map->FictionalCharacter {:name "Elizebeth Bennet" :appears-in "Pride & Prejudice" :author "Austen"}))
;; => #'user/elizebeth

Records are Maps

All map functions still work on records:-

(:name elizebeth)
;; => "Elizebeth Bennet"
(count elizebeth)
;; => 3
(keys watson)
;; => (:name :appears-in :author)

You can modify records

(def specific-watson (assoc watson :appears-in "Bourge-villas"))
;; => #'user/specific-watson
specific-watson
;; => {:name "John Watson", :appears-in "Bourge-villas", :author "Doyle"}

Aside, Doubt? Why doesnt specific-watson print as user.FictionalCharacter{:name "John Watson", :appears-in "Sherlock Holmes", :author "Doyle"}

You can add extra key value pair to a record They dont get the speed benefit though.

(def more-about-watson (assoc watson :address "211B Baker Street"))
;; => #'user/more-about-watson

Two advantages of Records

They fix the two disadvantages of maps.

  1. faster
  2. makes code clearer. ie, watson and watson2 are distinguishable
    (defrecord SuperComputer [cpu no-cpus storage-gb])
    ;; => user.SuperComputer
    (def watson2 (->SuperComputer "Power7" 2880 4000))
    ;; => #'user/watson2
    
    (class watson)
    ;; => user.FictionalCharacter
    (class watson2)
    ;; => user.SuperComputer
    
    (instance? SuperComputer watson2)
    ;; => true
        

BTW, class works on ALL values, not just records. Doubt, how is (class different from (type


Important Aside Dont do this

;;never:-
(defn process-thing [x]
  (if (= (instance? FictionalCharacter x))
    (process-fictional-character x)
    (process-computer x)))

^above will surely lead to spaghetti code. (Aside: Does Lit-Knuth agree with this?) Clojure’s way of dealing with such type sensitive code is using Protocols

Protocols

(defrecord Employee [first-name last-name department])
;; => user.Employee
(def alice (->Employee "Alice" "Smith" "Engineering"))
;; => #'user/alice

Both watson and alice have a name.

How can we treat both of them as people? Enter Protocols:- Make both of them implement the Person protocol.

(defprotocol Person
  (full-name [this])
  (greeting [this msg])
  (description [this]))
;; => Person

^Defines a protocol and also creates 3 polymorphic functions

Aside:- Polymorphic fn = what they do depends on the type of their first argument

Make both of them implement the Person protocol ie define the methods

(defrecord FictionalCharacter [name appears-in author]
  Person
  (full-name [this] (:name this))
  (greeting [this msg] (str msg " " (:name this)))
  (description [this] (str (:name this) " is a character in " (:appears-in this))))
;; => user.FictionalCharacter

(defrecord Employee [first-name last-name department]
  Person
  (full-name [this] (str first-name " " last-name))
  (greeting [this msg] (str msg " " (:first-name this)))
  (description [this] (str (:first-name this) " works in " (:department this))))
;; => user.Employee

Lets create instances and try them all out:-

(def sofia (->Employee "Sofia" "Diego" "Finance"))
;; => #'user/sofia
(def sonny (->FictionalCharacter "Sonny Corleone" "The Godfather" "Puzo"))
;; => #'user/sonny

(full-name sofia)
;; => "Sofia Diego"
(full-name sonny)
;; => "Sonny Corleone"

(greeting sofia "Sayonara, ")
;; => "Sayonara,  Sofia"
(greeting sonny "Sayonara, ")
;; => "Sayonara,  Sonny Corleone"

(description sofia)
;; => "Sofia works in Finance"
(description sonny)
;; => "Sonny Corleone is a character in The Godfather"

Decentralized Polymorphism using extend-protocol

After you make a new protocol,

(defprotocol Marketable
  (make-slogan [this]))
;; => Marketable

you can extend already defined records to conform to this new protocol. Without touching their original definitions. Hence decentralized.

Lets say we want to add a ‘Marketing slogan’ to each of our already defined Employee, FictionalCharacter and SuperComputer

(extend-protocol Marketable
  Employee (make-slogan [e] (str (:first-name e) "is the BEST employee!"))
  FictionalCharacter (make-slogan [fc] (str (:name fc) " is the GREATEST character!"))
  SuperComputer (make-slogan [sc] (str "This computer has " (:no-cpus sc) " CPUs!")))
;; => nil

You can even make data types that arent records to follow your protocol

(extend-protocol Marketable
  String (make-slogan [s] (str \" s \" " is a string! WOW!"))
  Boolean (make-slogan [b] (str b " is one of the two surviving Booleans!")))
;; => nil
(make-slogan sonny)
;; => "Sonny Corleone is the GREATEST character!"
(make-slogan true)
;; => "true is one of the two surviving Booleans!"

ie Records and Protocols are decoupled they are independent of each other in time and ?space

Records-Protocols vs OOP

Analogy/Resemblance:- Records ~ Classes Record instances ~ Objects Protocols ~ Abstract Interfaces

Protocols are Clojure’s riff on type-based polymorphism {Type-based polymorphism definition - the idea that you can have a single operation implemented in different ways by different types.}

But differences:- Unlike objects, Record instances are immutable Unlike classes, Records have no concept of inheritance. ie there are no Super Record types.

Many significant Clojure programs dont feel the necessity for records and protocols Therefore, avoid premature usage of records and protocols, just use simple maps & functions

Protocols vs Multimethods

One Similarity:- both of them let you express Polymorphism

Two differences:- Multimethods can branch out (dispatch) based on any criterion, not just type Protocols always branch out based on type

Multimethods define a single operation Protocols refer to a collection of operations.

One Usage tip:- Multimethod is a more generic concept than Protocols. Design choice: If Protocol suffices, avoid using a multimethod in its place.

In the wild

Clostache templating library Clostache library uses just ONE record type. It uses NO protocols, NO polymorphism. Its just simple maps and functions. This is typical of Clojure projects. It has 318 stars, ?but is widely used.

Stuart Sierra’s Component library It uses just ONE protocol It has 2.1k stars, and is the most used library for startup management.

reify creates a one-off protocol-instance without defining a protocol. Its helpful for testing purposes. Such instances are permitted to implement a protocol partially. ie, omit some methods.

Staying out of trouble

Records

When using (map->SomeRecord some_map), if you mis-name a key of its argument, that key-value pair will end up as an extra key-value. This missed key’s value will be set to nil. tldr: carefully match argument keys with defrecord slot names.


Botched assoc due to similar mis-name of key will, similarly, result in an extra key-value pair and a nil-led pair.

Protocols

defprotocol adds functions to the namespace. Be careful of function-name collisions with builtin functions.

Two defprotocols might add function names that might collide with each other’s.

When in doubt, put each protocol in its own namespace.


Records have a more generic cousin0 named Types deftype A Record comes with a map and fast access etc. Whereas, a ‘type’ is more of a blank slate.

Types are almost never used in Clojure

Wrapping up

Records are specialized maps. Protocols are collection of functions, that can be used to define polymorphic operatons /on/ records

Tests

Tests make sure your code works.

Eg: We have an inventory of books. We want to write two functions.

  1. find book-x by title
  2. find number of copies of a book-x

where book-x is one of the books from the inventory.

We learn clojure.test and clojure.check by setting up tests for the above two.

clojure.test

lein new inventory

What inventory looks like:-

[{:title "2001" :author "Clarke" :copies 21}
 {:title "Emma" :author "Austen" :copies 10}
 {:title "Misery" :author "King" :copies 101}]
(ns inventory.core)

(defn find-by-title
  "Search for a book by title, where title is a string and books is a collection of book maps, each of which must have a :title entry"
  [title books]
  (some #(if (= (:title %) title) %) books)) ;;Recall that the ~some~ returns whatever the first truthy predicate returns 
;; => #'user/find-by-title

(defn number-of-copies-of
  "Return the number of copies in inventory of the given title, where title is a string and books is a collection of book maps each of which must have a :title entry"
  [title books]
  (:copies (find-by-title title books)))
;; => #'user/number-of-copies-of

Also, by convention, Clojure orders the function arguments such that, collection is the last argument.

Where should I put my tests?

The convention is to put them in a ‘parallel’ module; and suffix a -test to the end of the namespace. (Recall that - becomes _ for filenames) Whats parallel:- ./inventory/src/inventory/core.clj Parallel to it is ./inventory/test/inventory/core_test.clj

(ns inventory.core) Suffixing it is (ns inventory.core-test)

deftest

deftest lets us wire up the test

(ns inventory.core-test
  (:require [clojure.test :refer :all])
  (:require [inventory.core :as i]))

(def books [{:title "2001" :author "Clarke" :copies 21}
            {:title "Emma" :author "Austen" :copies 10}
            {:title "Misery" :author "King" :copies 101}])

(deftest test-finding-books
  (is (not (nil? (i/find-by-title "Emma" books)))))

;;(deftest test-something-that-fails
;;  (is (not (nil? (i/find-by-title "Bla Bla Blue" books)))))

;;can include more than one condition inside a single deftest
(deftest test-finding-books-better
  (is (not (nil? (i/find-by-title "Emma" books))))
  (is (nil? (i/find-by-title "Bla Bla Blue" books))))

;;organise tests into subtests using ~(testing~
(deftest test-basic-inventory
  (testing "Finding books"
    (is (not (nil? (i/find-by-title "Emma" books))))
    (is (nil? (i/find-by-title "Bla Bla Blue" books))))
  (testing "Copies in inventory"
    (is (= 10 (i/number-of-copies-of "Emma" books)))))

^Notice that since unit-tests are argumentless predicates, deftest syntax doesnt have any argument list.

A dev directory is conventionally used to store REPL commands etc

mkdir dev

Run those tests

(require '[inventory.core-test :as ct])

(ct/test-finding-books)

Run all the tests

(clojure.test/run-tests ...

(require 'inventory.core-test)          ;?because tests arent loaded by default in a lein repl
(require 'clojure.test)

(ns inventory.core-test)                     ;select this ns
(clojure.test/run-tests)                ;runs all tests in current ns
(clojure.test/run-tests *ns*)

;;below can be run from any namespace
(clojure.test/run-tests 'inventory.core-test)

Using lein

lein test

External library name is test.check Add [org.clojure/test.check "1.1.1"] to project.clj

sample is hardcoded to return next 10 values from a generator

(require '[clojure.test.check.generators :as gen])
(gen/sample gen/string-alphanumeric)

inventory.core> (gen/sample gen/string-alphanumeric) (“” “” “” “83” “L5” “9” “” “uErvu1” “R8Gc6eN” “A2h”)

To make C-x C-e work:- Start a cider repl inside the inventory folder Then connect to it using M-x cider-connect , pick localhost.

We can get rid of empty string using gen/such-that that accepts a predicate checker

(def title-gen (gen/such-that not-empty gen/string-alphanumeric))
;; => #'inventory.core/title-gen
(def author-gen (gen/such-that not-empty gen/string-alphanumeric))
;; => #'inventory.core/author-gen
(def copies-gen (gen/such-that (complement zero?) gen/pos-int))
;; => #'inventory.core/copies-gen

Above generators can be combined

;;lets build a book generator
(def book-gen
  (gen/hash-map :title title-gen :author author-gen :copies copies-gen))
;; => #'inventory.core/book-gen

Try it out

(gen/sample book-gen)
;; => ({:title "Q", :author "m0", :copies 2}
;;     {:title "U9", :author "b", :copies 1}
;;     {:title "7", :author "zC", :copies 1}
;;     {:title "h", :author "wM3", :copies 1}
;;     {:title "xu", :author "67w", :copies 3}
;;     {:title "pSK", :author "1zy2g", :copies 1}
;;     {:title "6bNV", :author "WNkS", :copies 2}
;;     {:title "vlr488q", :author "TeC", :copies 8}
;;     {:title "V9", :author "eiwf05Mn", :copies 6}
;;     {:title "W7vQUGgy", :author "R1", :copies 7})

gen/vector spits out a vector whose elements are chosen from generator

(gen/sample (gen/vector book-gen))
;; => ([]
;;     []
;;     [{:title "p", :author "0t", :copies 2}]
;;     [{:title "Q", :author "Flm", :copies 1}]
;;     [{:title "27Y", :author "Fzu", :copies 3}
;;      {:title "k47", :author "dm", :copies 4}
;;      {:title "G", :author "6X3", :copies 2}
;;      {:title "zEvh", :author "hj37", :copies 4}]
;;     [{:title "M4ML", :author "4Y6", :copies 3}]
;;     [{:title "VoQGop", :author "j2", :copies 1}
;;      {:title "6J", :author "6KIJ", :copies 1}
;;      {:title "3", :author "q3Z", :copies 3}
;;      {:title "S6", :author "b438L", :copies 3}
;;      {:title "E3CAC2", :author "s0F7T6", :copies 5}
;;      {:title "eI", :author "516", :copies 2}]
;;     [{:title "Lzvk", :author "81Z3", :copies 2}
;;      {:title "K", :author "1AhmIc9", :copies 7}
;;      {:title "vmu5", :author "I", :copies 3}
;;      {:title "uY1", :author "e953", :copies 4}
;;      {:title "sJ81Dg1", :author "24CA8", :copies 5}
;;      {:title "569Qu", :author "73q", :copies 7}]
;;     [{:title "5", :author "9L7Cn0k", :copies 4}
;;      {:title "W7e53u", :author "WF85", :copies 8}
;;      {:title "9", :author "355fT", :copies 3}
;;      {:title "37QW3", :author "g2ifC", :copies 7}]
;;     [{:title "195HHa", :author "9", :copies 7}
;;      {:title "m", :author "4j583P6qy9", :copies 3}
;;      {:title "MNqIwA3", :author "7o5", :copies 7}
;;      {:title "Q7rH9s7", :author "k9t1", :copies 1}
;;      {:title "L", :author "N1362h", :copies 6}
;;      {:title "yS1eNG", :author "0xC00jud", :copies 1}])

Lets get rid of empty vectors, aka, empty inventories

(gen/sample (gen/such-that not-empty (gen/vector book-gen)))
;; => ([{:title "p2", :author "9d", :copies 1}]
;;     [{:title "tLt4", :author "329", :copies 1}]
;;     [{:title "I", :author "q8dQ", :copies 3}]
;;     [{:title "5", :author "5V", :copies 1}]
;;     [{:title "9eq", :author "00r4m", :copies 3}
;;      {:title "I", :author "H6", :copies 6}]
;;     [{:title "p", :author "0", :copies 2}]
;;     [{:title "m628", :author "9qx", :copies 1}
;;      {:title "s7h60pP", :author "Hp", :copies 2}
;;      {:title "0V", :author "44me", :copies 6}]
;;     [{:title "C1vs8H", :author "S7", :copies 1}
;;      {:title "34p8G", :author "3hg", :copies 1}]
;;     [{:title "948pp", :author "ZId2", :copies 4}
;;      {:title "wCz7W", :author "0", :copies 6}
;;      {:title "SLN42EdLI5", :author "iBLp", :copies 1}
;;      {:title "1", :author "4VwE7", :copies 7}
;;      {:title "7", :author "Ildxd1j5", :copies 7}
;;      {:title "27n11", :author "81o1dw", :copies 3}
;;      {:title "Sq1Ys2tG", :author "VbobU8H", :copies 8}
;;      {:title "0jd", :author "5", :copies 6}]
;;     [{:title "5j", :author "ga63AME", :copies 2}
;;      {:title "CE", :author "6", :copies 9}
;;      {:title "7", :author "wBP", :copies 3}])

(gen/not-empty does the same as (gen/such-that not-empty

(def inventory-gen (gen/not-empty (gen/vector book-gen)))
;; => #'inventory.core/inventory-gen

Aside doubt: Are all simple Clojure maps implemented as hashmaps?

Think again about what one testcase consists of:-

  • An inventory of books
  • One book from amongst them

To test our two functions, We also need a single book from each of the generated inventories. aka, we want to pluck a book from the inventory This can be done using gen/elements

(gen/let [inventory inventory-gen
          book (gen/elements inventory)]
  {:inventory inventory :book book})
;; => {:gen #function[clojure.test.check.generators/gen-bind/fn--5190]}

Lets bind it

(def inventory-and-book-gen (gen/let [inventory inventory-gen
                                      book (gen/elements inventory)]
                              {:inventory inventory :book book}))
;; => #'inventory.core/inventory-and-book-gen

Try it

(gen/sample inventory-and-book-gen)
;; => ({:inventory [{:title "7", :author "s", :copies 1}],
;;      :book {:title "7", :author "s", :copies 1}}
;;     {:inventory [{:title "u", :author "y", :copies 3}],
;;      :book {:title "u", :author "y", :copies 3}}
;;     {:inventory
;;      [{:title "j", :author "Np", :copies 2}
;;       {:title "mv", :author "7u", :copies 2}],
;;      :book {:title "j", :author "Np", :copies 2}}
;;     {:inventory
;;      [{:title "g67", :author "2G5L", :copies 1}
;;       {:title "2o1", :author "qvj", :copies 3}
;;       {:title "Ej3", :author "y1", :copies 4}],
;;      :book {:title "Ej3", :author "y1", :copies 4}}
;;     {:inventory [{:title "a", :author "0h", :copies 3}],
;;      :book {:title "a", :author "0h", :copies 3}}
;;     {:inventory
;;      [{:title "d", :author "Qlauo", :copies 5}
;;       {:title "C8xMx", :author "6Z3", :copies 1}
;;       {:title "SG", :author "lLb", :copies 5}],
;;      :book {:title "SG", :author "lLb", :copies 5}}
;;     {:inventory [{:title "sB", :author "xoQFL3P8", :copies 7}],
;;      :book {:title "sB", :author "xoQFL3P8", :copies 7}}
;;     {:inventory
;;      [{:title "0oril", :author "hi7KX", :copies 5}
;;       {:title "9C", :author "N1LqU", :copies 6}
;;       {:title "ONg8", :author "wsMtv8v", :copies 1}
;;       {:title "osN4333", :author "2VE", :copies 3}
;;       {:title "t3", :author "VY", :copies 7}],
;;      :book {:title "t3", :author "VY", :copies 7}}
;;     {:inventory
;;      [{:title "U47d3I40r", :author "li", :copies 7}
;;       {:title "Lu2q2L4yC", :author "a", :copies 4}
;;       {:title "Nys1Xa8J", :author "PY85", :copies 8}
;;       {:title "zucCZ7p", :author "51t0uJvo", :copies 6}
;;       {:title "E97O", :author "3", :copies 1}
;;       {:title "8OH", :author "dvcX", :copies 7}
;;       {:title "hQnfo1rs0", :author "AysSC8CK7", :copies 4}],
;;      :book {:title "Nys1Xa8J", :author "PY85", :copies 8}}
;;     {:inventory
;;      [{:title "GVD1", :author "gbzb", :copies 1}
;;       {:title "J1VYKsX", :author "SuImD", :copies 5}
;;       {:title "Pg03A9vX6", :author "jYh9x8WU1", :copies 5}
;;       {:title "4r8E8", :author "05PCga3", :copies 7}
;;       {:title "DQ", :author "bsz7br8", :copies 10}],
;;      :book {:title "4r8E8", :author "05PCga3", :copies 7}})

Now, we have completed generating the data for testing.

Checking Properties - aka Running Generated Testcases

Aside: The author has more or less plagiarized Introduction to test.check

Let us take two toy examples to understand expressing the property to be tested

Each positive integer is smaller than the next positive integer

(require :reload '[clojure.test.check.properties :as prop])
;; => nil

Lets express the property.

Let us wire up an infinite number of testcases

(prop/for-all [i gen/pos-int]
  (< i (inc i)))
;; => {:gen #function[clojure.test.check.generators/gen-fmap/fn--5185]}

Lets limit the infinite number of cases to say 50 clojure.test.check/quick-check will randomly pick 50

(require :reload '[clojure.test.check :as tc])
;; => nil
(tc/quick-check 50
  (prop/for-all [i gen/pos-int]
    (< i (inc i))))
;; => {:result true,
;;     :pass? true,
;;     :num-tests 50,
;;     :time-elapsed-ms 1,
;;     :seed 1723931317348}

Let us apply what we learnt to check our book inventory:-

(require '[clojure.test.check.properties :as prop])
(require '[clojure.test.check :as tc])
(require '[inventory.core :as i])

(tc/quick-check 50
  (prop/for-all [i-and-b inventory-and-book-gen]
    (= (i/find-by-title (->> i-and-b :book :title) (->> i-and-b :inventory))
      (:book i-and-b))))
;; => {:result true,
;;     :pass? true,
;;     :num-tests 50,
;;     :time-elapsed-ms 71,
;;     :seed 1723931998480}

Aside:- Below is valid too, because maps themselves are functions too

(tc/quick-check 50
  (prop/for-all [i-and-b inventory-and-book-gen]
    (= (i/find-by-title (-> i-and-b :book :title) (-> i-and-b :inventory))
      (:book i-and-b))))
;; => {:result true,
;;     :pass? true,
;;     :num-tests 50,
;;     :time-elapsed-ms 63,
;;     :seed 1723932041896}

If need be, we can integrate the above tests with clojure.test

(ns inventory.core-gen-test)

(require :reload '[clojure.test.check.properties :as prop])
(require :reload '[clojure.test.check :as tc])
(require :reload '[inventory.core :as i])
(require :reload '[clojure.test.check.clojure-test :as ctest])

<<Define the generator>>

(ctest/defspec find-by-title-finds-books 50
  (prop/for-all [i-and-b inventory-and-book-gen]
    (= (i/find-by-title (->> i-and-b :book :title) (->> i-and-b :inventory))
      (:book i-and-b))))

Run it

(find-by-title-finds-books)
;; => {:result true,
;;     :pass? true,
;;     :num-tests 50,
;;     :time-elapsed-ms 75,
;;     :seed 1723985591822}

As ususal lein test will run all clojure.test in all namespaces.

In the wild

file:clojure/test/clojure/test_clojure/numbers.clj::(deftest test-add file:clojure/test/clojure/test_clojure/sequences.clj::(deftest test-cons

and is useful to build parameterized tests. Its a sister of (is

Spec

Clojure is more concerned with the shape of the data than its type.

clojure.spec/valid?, clojure.spec/or, clojure.spec/and

clojure.spec is a library for verifying the shape of data. its kinda like a regular expression library for clojure data.

(ns inventory.core
  (:require [clojure.spec.alpha :as s]))

^Above library comes bundled with Clojure language.

(s/valid? number? 44)
;; => true
(s/valid? number? :somekeyword)
;; => false

s/valid? accepts a predicate function We call those predicate functions as a spec

s/and and s/or helps build such predicate functions.

n stands for number, not not

(def n-gt-10 (s/and number? #(> % 10)))
;; => #'inventory.core/n-gt-10

(s/valid? n-gt-10 1)
;; => false
(s/valid? n-gt-10 10)
;; => false
(s/valid? n-gt-10 11)
;; => true

n-gt-10 is a spec

(def n-gt-10-lt-100 (s/and number? #(> % 10) #(< % 100)))
;; => #'inventory.core/num-gt-10-lt-100
(s/valid? n-gt-10-lt-100 55)
;; => true

The argument list to ~s/or~ has a twist

;;wrong:-
;;(def num-or-str (s/or number? string?))

(def n-or-s (s/or :a-number number? :a-string string?))
;; => #'inventory.core/num-or-str
(s/valid? n-or-s "Hello")
;; => true
(s/valid? n-or-s 99)
;; => true
(s/valid? n-or-s true)
;; => false

The keywords are required to give descriptive feedback whenever a spec fails. We call this as a spec with descriptive keywords

s/and and s/or accepts either of normal predicates or other specs.

(def n-gt-10-or-symbol (s/or :greater-10 n-gt-10 :a-symbol symbol?))

Spec-ing collections

(def coll-of-strings (s/coll-of string?))
;; => #'inventory.core/coll-of-strings
(def coll-of-n-or-s (s/coll-of n-or-s))
;; => #'inventory.core/coll-of-n-or-s

s/cat helps build specs that say this should follow that in a collection. Doubt:- Why is it named so?

Four element collections of the form:- string, number, string, number

(def s-n-s-n (s/cat :s1 string? :n1 number? :s2 string? :n2 number?))
;; => #'inventory.core/s-n-s-n
(s/valid? s-n-s-n ["Emma" 1815 "Jaws" 1974])
;; => true

Specs for maps, for key existence:-

;;define the book spec
(def book-s
  (s/keys :req-un [:inventory.core/title
                   :inventory.core/author
                   :inventory.core/copies]))
;; => #'inventory.core/book-s
(s/valid? book-s {:title "Emma" :author "Austen" :copies 10})
;; => true
(s/valid? book-s {:title "Arabian Nights" :copies 17})
;; => false

;;additional keys are permitted
(s/valid? book-s {:title "Emma" :author "Austen" :copies 10 :published 1968})
;; => true

:req-un :- ‘-un’ stands for unqualified keywords. ie We suppy qualified keywords to s/keys function, and ask it to look for unqualified keywords in the map.

Why are fully qualified keywords used to define map specs?

Specs are meant to be registered to a global registry. This means, we need to use fully qualified keynames. If not, there could be collisions in keywords.

Registering Specs

After registering to a JVM wide registry, any code can use my specs. clojure.spec/def does the registration into the global registry (not to be confused with def!)

A spec is registered at a ‘keyword’

(s/def :inventory.core/book
  (s/keys :req-un [:inventory.core/title
                   :inventory.core/author
                   :inventory.core/copies]))
;; => :inventory.core/book

Then on, you can use that keyword as a spec globally

(s/valid? :inventory.core/book {:title "Dracula" :author "Stoker" :copies 10})
;; => true

Recall that :: is a shorthand for the current namespace, Therefore, above could also be re-written as:-

(s/def ::book
  (s/keys :req-un [::title
                   ::author
                   ::copies]))
;; => :inventory.core/book

However, above spec says nothing above the values associated with the keys in above map

(s/valid? ::book {:title 1234 :author false :copies "many"})
;; => true

^Lets fix this

Specs for maps, for type of value:-

We have already defined a spec for :inventory.core/book If we register a spec for each of the above map’s keys, then s/valid? will automatically also ensure that the map’s values match the above specs.

(s/def ::title string?)
;; => :inventory.core/title
(s/def ::author string?)
;; => :inventory.core/author
(s/def ::copies number?)
;; => :inventory.core/copies
(s/def ::book (s/keys :req-un [::title ::author ::copies]))
;; => :inventory.core/book
(s/valid? ::book {:title 1234 :author false :copies "many"})
;; => false
(s/valid? ::book {:title "Dracula" :author "Stoker" :copies 10})
;; => true

explain-ing why a particular spec didnt match

Output happens by a printout

(s/explain n-gt-10 1)
;; => nil
(s/valid? ::book {:author :austen :title :emma})
;; => false
(s/explain ::book {:author :austen :title :emma})
;; => nil

conform is a nice truthy function:- If a spec fails, it returns the specific keyword :clojure.spec.alpha/invalid

(s/conform n-gt-10 1)
;; => :clojure.spec.alpha/invalid

If it matches, it returns the value itself for simple specs & for specs with descriptive keywords returns a map of ‘spec descriptive’ keywords to collection values.

(s/conform number? 1968)
;; => 1968
(s/conform s-n-s-n ["Emma" 1815 "Jaws" 1974])
;; => {:s1 "Emma", :n1 1815, :s2 "Jaws", :n2 1974}

Function specs

Above facilitates a spec matching at critical points in your code

Choice1

:pre and :post keywords inside defn

Let us say we want to ensure the shape of :title and :inventory passed to the find-by-title function:-

(s/def ::inventory (s/coll-of ::book))
;; => :inventory.core/inventory

(defn find-by-title
  "Search for a book by title, where title is a string and books is a collection of book maps, each of which must have a :title entry"
  [title books]
  {:pre [(s/valid? ::title title)
         (s/valid? ::inventory books)]}
  (some #(if (= (:title %) title) %) books))
;; => #'inventory.core/find-by-title

Choice2

Keeps the spec seperate from the defn

(defn find-by-title [title books]
  (some #(if (= (:title %) title) %) books))
;; => #'inventory.core/find-by-title

Register a spec for the above function using fdef

(s/fdef find-by-title
  :args (s/cat :title ::title :inventory ::inventory))
;; => inventory.core/find-by-title

Mnemonic to remember fdef , ”Final-specs-def will be registered”

?Unlike :pre and :post, we need to turn on the above wired up specs

(require '[clojure.spec.test.alpha :as st])
;; => nil
(st/instrument 'inventory.core/find-by-title)
;; => [inventory.core/find-by-title]

See it in action

(find-by-title "Emma" ["Emma" "2001" "Jaws"])
;; => Execution error - invalid arguments to inventory.core/find-by-title at (form-init3142513827429707114.clj:253).
;;    "Emma" - failed: map? at: [:inventory] spec: :inventory.core/book
;;    "2001" - failed: map? at: [:inventory] spec: :inventory.core/book
;;    "Jaws" - failed: map? at: [:inventory] spec: :inventory.core/book

Spec based things can slow things down in production. Prefer to use it only during development and testing.

Generative tests that use the specs to auto generate test cases

Specs provides sufficient information to generate a random test case.

Lets say we want to test the below function

(defn book-blurb [book]
  (str "The best selling book " (:title book) " by " (:author book)))
;; => #'inventory.core/book-blurb

Lets wire up a test using choice2

(s/fdef book-blurb
  :args (s/cat :book ::book))
;; => inventory.core/book-blurb

clojure.spec.test.alpha/check will generate 1000 random testcases and run them

(require '[clojure.spec.test.alpha :as st])
;; => nil
(st/check 'inventory.core/book-blurb)
;; => ({:spec #object[clojure.spec.alpha$fspec_impl$reify__2518 0x56f46dc8 "clojure.spec.alpha$fspec_impl$reify__2518@56f46dc8"], :clojure.spec.test.check/ret {:result true, :pass? true, :num-tests 1000, :time-elapsed-ms 331, :seed 1724187907172}, :sym inventory.core/book-blurb2})

:ret check the return value

(s/fdef book-blurb
  :args (s/cat :book ::book)
  :ret (s/and string? (partial re-find #"The best selling")))
;; => inventory.core/book-blurb

Run it

(st/check 'inventory.core/book-blurb)
;; => ({:spec #object[clojure.spec.alpha$fspec_impl$reify__2518 0x451e7e8a "clojure.spec.alpha$fspec_impl$reify__2518@451e7e8a"], :clojure.spec.test.check/ret {:result true, :pass? true, :num-tests 1000, :time-elapsed-ms 418, :seed 1724187834720}, :sym inventory.core/book-blurb})

The :fn key gets handed a map

{:args <>, :ret <>} containing both the function arguments and the return value. Lets add a predicate for that map

;;Ensures that the authors name is part of the blurb output
(defn check-args-and-return [{:keys [args ret]}]
  ;;Since we have access to both the ret and the args
  ;;we can write tests that use both the above as parameters

  ;;Eg: check that arg-string is part of the ret-string:-
  (let [author (-> args :book :author)]
    (not (neg? (.indexOf ret author))))
  )
;; => #'inventory.core/check-args-and-return

(s/fdef book-blurb
  :args (s/cat :book ::book)
  :ret (s/and string? (partial re-find #"The best selling"))
  :fn check-args-and-return)
;; => inventory.core/book-blurb

Run it

(st/check 'inventory.core/book-blurb)
;; => ({:spec #object[clojure.spec.alpha$fspec_impl$reify__2518 0x1c8bd1cd "clojure.spec.alpha$fspec_impl$reify__2518@1c8bd1cd"], :clojure.spec.test.check/ret {:result true, :pass? true, :num-tests 1000, :time-elapsed-ms 364, :seed 1724188840826}, :sym inventory.core/book-blurb})

Staying out of trouble

If your architecture consists of multiple JVM, ensure to register your specs in each of them.

In the wild

ring-spec contains specs for Ring web applicaiton

clojure.specs.alpha contains specs for Clojure itself s/? makes the next part of the spec optional.

Part III ---------------------------------------------------------------------------------------------------

Interoperating with Java

In Java we use classes to make objects we use objects to make things happen

A peek at Java

Java is all about objects.

Class -> Object There is a constructor method in each class that runs at object creation.

public class Book {
    public String title;
    public String author;
    public int numberChapters;

    //The constructor method
    public Book(String t, String a, String nChaps) {
        title = t;
        author = a;
        numberChapters = numChaps;
    }
}
  1. Most of the functionality in Java is bundled as methods. Eg: publish, payRoyalties
  2. Above way of exposing data fields using public is bad. Prefer a getter and setter
public class Book {
    private String title;
    private String author;
    private int numberChapters;

    //The constructor method
    public Book(String t, String a, String nChaps) {
        title = t;
        author = a;
        numberChapters = numChaps;
    }
    //^Notice that its return type isnt set
    
    public void publish() {
        // Do something to publish the book
    }

    public void payRoyalties() {
        // Do something to pay royalties
    }

    //Getter and Setter methods to make fields accessible from outside the class
    public String getTitle() {
        return title;          //optionally could have written it as:- this.title
    }
    public String getAuthor() {
        return author;
    }
    public int getNumberChapters() {
        return numberChapters;
    }
}

^Terminology:- t, a, nChaps are called as parameters to the constructor title, author, numberChapters are called as class attributes


Java organizes classes into an Inheritance tree. Every class has a parent, aka superclass Only the topmost class of this tree: java.lang.Object, has no parent

public class Rectangle {
    private int length;
    private int breadth;
    public Rectangle(int l, int b) {
        length = l;
        breadth = b;
    }
}

public class Square extends Rectangle {
    private int side; // L=B=side
    public Square(int s) {
        side = s;
        super(side, side);
    }
}

A group of classes are bundled as a package. These are analogous to Clojure namespaces.

package com.anup.mygeometry;

public class Rectangle {
  ...
}

public class Square extends Rectangle {
  ...
}

And Back to Clojure

Using Java from Clojure, an example: java.io.File

While calling the Java constructor, notice the dot at the end of the classname

(def authors (java.io.File. "authors.txt"))
;; => #'user/authors

(if (.exists authors)
  (println "Our authors file is there")
  (println "Our authors file is missing"))

(if (.canRead authors)
  (println "We can read it"))

change permissions

(.setReadable authors true)
;; => true

^Notice that we call a method with the object/instance as the first argument, and method parameters as arguments 2,3,4…etc.

The method name is prefixed with a dot


Accessing java object’s public fields, (.-<field> <instance>) Eg

(def rect (java.awt.Rectangle. 0 0 10 20))
;; => #'user/rect

(.-width rect)
;; => 10
(.-height rect)
;; => 20

(:import Packages

Importing Java classes lets us do away with always requiring fully qualified classnames

(ns read-authors
  (:import java.io.File))
;; => nil

;;Henceforth, no need to write the full java.io.File verbose name:-
(File. "authors.txt")
;; => #object[java.io.File 0x3e070441 "authors.txt"]

No quoting is needed to the argument passed to :import

^Its repl equivalent:-

(import java.io.File)

^no quoting needed for argument

These are analogous to :require and (require that were used for loading Clojure namespaces.


Importing multiple classes from the same java package

(ns read-authors
  (:import (java.io File InputStream)))

^no quoting needed for argument seq

or, in the repl as

(import '(java.io File InputStream))

^quoting is needed.


Clojure automatically imports all classes in java.lang into all namespaces.

String
;; => java.lang.String
Boolean
;; => java.lang.Boolean

Class methods and Class fields

aka static methods and static fields

?For Metaprogramming facility, java classes themselves are instances of a class called java.lang.Class

Static methods and static fields are both accessed by the same syntax: <classname>/<field or method name> Eg:-

(ns read-authors
  (:import java.io.File))
;; => nil
(File/separator)
;; => "/"

(File/createTempFile "authors_list" ".txt")
;; => #object[java.io.File 0x7d908e33 "/tmp/authors_list7852999848716380022.txt"]

In the wild (Important)

Exploring a Java library’s API

Eg:- Google Gson library Include the library in project.clj

REPL is a great place to explore a java library, alongside reading that library’s documentation.

(import com.google.gson.Gson)
;; => com.google.gson.Gson

(def gson-obj (Gson.))
;; => #'exploregson.core/gson-obj
(.toJson gson-obj 44)      ;;its Java equivalent is gson-obj.toJson(44)
;; => "44"
(.toJson gson-obj {:title "1984" :author "Orwell"})
;; => "{\":title\":\"1984\",\":author\":\"Orwell\"}"

Exploring Clojure implementation in Java

At its very basic,

  1. Clojure is just a collection of Java classes.
  2. Every Clojure value is just a reference to some Java object.

This 2 is the reason why we can pass Clojure values as parameters to Java methods.


If a Clojure value is just an instance of some Java object, then, We should be able to call Java methods of that Java class on a Clojure value:-

(def v [1 2 3])
;; => #'exploregson.core/v
(.count v)
;; => 3

^The implication is beautiful:- We can explore Clojure itself using Clojure


This also explains the code from Chapter 5

(ns user)
;; => nil

(def author "Dickens")                  ;Make a var
;; => #'user/author
(def the-var #'author)                  ;Grab the var
;; => #'user/the-var

(.get the-var)                          ;Pull the value out of the var
;; => "Dickens"

(.-sym the-var)                         ;Pull the symbol out of the var
;; => author

Lets explore the random question:- What do I get back from a cons?

(def c (cons 99 [1 2 3]))
;; => #'user/c
(class c)
;; => clojure.lang.Cons

So, I need to read this Java file:- ./clojure/src/jvm/clojure/lang/Cons.java I see Java methods in that file, let me call them and see what happens

(.first c)
;; => 99
(.more c)
;; => (1 2 3)

^In this way, slowly explore Clojure’s implementation.

Staying Out of Trouble (Important)

.some_java_method and SomeClass/some_static_java_method are not the same as Clojure functions. They are special forms They are not first class.


memfn can turn a java method into a clojure function.

(ns read-authors
  (:import java.io.File))
;; => nil
(def files [(File. "authors.txt") (File. "titles.txt")])
;; => #'read-authors/files
(map (memfn exists) files)
;; => (true false)

(memfn abrakadabra)
;; => #function[read-authors/eval8180/fn--8181]
(memfn exists)
;; => #function[read-authors/eval8185/fn--8186]

Dont un-necessarily wrap java methods into clojure functions. Just directly call the java method. Interop is native Clojure. Use it. So, directly call the Playwright Java functions when implementing AppStoreRank.


Unlike Clojure, many java objects are mutable. Lets say you are forced to deal with mutable Java objects. Eg:- Some Java API returns a mutable object Try to turn it into an immutable Clojure value.

Eg:- Lets say the Java API returns a java.util.Vector mutable type Say, a vector named anup-favorite-books

(def immutable-anup-favorite-books (vec anup-favorite-books))

Just like that, you are back in the beautiful immutable world.

Wrapping up

nil

Threads, Promises and Futures

We will learn how to use Java threads from Clojure.

?Doubt: Does -main prefixed by a dash imply that it is supposed to be compiled? WTF is meant by compiling in Java land? Is Java a compiled language?

This chapter and the next are closely related. Threads and mutable State.

Great Power

BTW, every Clojure program runs on some java thread, called the single main thread.

java.lang.Thread , its .start method:-

(defn do-something-in-a-thread []
  (println  "Hello from the thread")
  (Thread/sleep 3000)
  (println "Good bye from the thread"))
;; => #'user/do-something-in-a-thread

(def the-thread (Thread. do-something-in-a-thread))
;; => #'user/the-thread

(.start the-thread)
(println "The main thread")

^Org babel doesnt handle the last print gracefully. Run it in a repl, and you will see a delayed goodbye printout after the above two printouts.

Thread. constructor accepts any object that implements the Runnable interface. Conveniently, all Clojure functions implement the Runnable interface. ie do-something-in-a-thread is a valid argument to Thread.

The call to .start will run a thread separate from the main thread.

Great Responsibility

Race conditions can occur when multiple threads access/modify a mutable object. File systems, Database tables..etc are mutable.

Good fences make happy threads

(def inventory [{:title "Emma" :sold 51 :revenue 255}
                {:title "2001" :sold 17 :revenue 170}])
;; => #'user/inventory

Lets say we wanted to run some computations of total sales and total revenue, in parallel threads

(.start (Thread. (sum-copies-sold inventory))) ;I suspect a syntax error in this and next line
(.start (Thread. (sum-revenue inventory)))     ;in the argument to Thread.

Since we know that inventory is immutable, it gives us a lot of confidence to pass it around to different threads without any worry.

Aside:- Notice that Thread. accepts a function name if it has no arguments, and, a bracketed function call along with arguments if there are. Doubt:- xournalpp:Thread constructor argument doubt.xopp Therefore, the proper syntax should have been

(.start (Thread. #(sum-copies-sold inventory))) ;notice the hash
(.start (Thread. #(sum-revenue inventory)))     

Recall the dynamic vars that we learn in Chapter 8, Since their value changes only inside a binding, they play well with threads. Eg:-

(def ^:dynamic *favorite-book* "Oliver Twist")
;; => #'user/*favorite-book*
(def thread-1 (Thread. #(binding [*favorite-book* "2001"]
                          (println "My favorite book is" *favorite-book*))))
;; => #'user/thread-1

(def thread-2 (Thread. #(binding [*favorite-book* "Emma"]
                          (println "My favorite book is" *favorite-book*))))
;; => #'user/thread-2

(.start thread-1)
(.start thread-2)

At the start of both the threads, “Oliver Twist” remains the initial value of the dynamic var. ie, one of the threads doesnt conflict with the other.


Therefore, the immtuability and per-thread dynamic variable binding helps us avoid race conditions.

Promise

Related Aside:- join lets you wait until the thread passed to it finishes.

(def del-thread (Thread. #(Thread/sleep 3000)))
;; => #'user/del-thread
(.start del-thread)                     ;starts the thread in the background
;; => nil
(.join del-thread)                      ;pauses the main thread until above thread finishes
;; => nil

promise is a lot more practical than join


The promise is a value trap that is initially empty, and through deliver you can set the value of that empty slot.

(def a-promise (promise))
;; => #'user/a-promise
a-promise
;; => #<Promise@6179a15f: :not-delivered>
(deliver a-promise "Bla bla bla")
;; => #<Promise@6179a15f: "Bla bla bla">
a-promise
;; => #<Promise@6179a15f: "Bla bla bla">
a-promise
;; => #<Promise@6179a15f: "Bla bla bla">

Henceforth, a-promise continues to carry the value “Bla bla bla”


(deref and @ can get at the value stored in a promise

(deref a-promise)
;; => "Bla bla bla"
(identity @a-promise)
;; => "Bla bla bla"

^Aside: I resort to using the identity function because, if not, C-x C-e mechanism gets confused.

Important feature of a promise A deref or @ will pause until there is a value. This feature can be used to control waiting until certain thread returns. Eg:-

(def inventory [{:title "Emma" :sold 51 :revenue 255}
                {:title "2001" :sold 17 :revenue 170}])
;; => #'user/inventory
(defn sum-copies-sold [inv]
  (apply + (map :sold inv)))
;; => #'user/sum-copies-sold
(defn sum-revenue [inv]
  (apply + (map :revenue inv)))
;; => #'user/sum-revenue

Try1 (fail)

(def copies-thread (Thread. #(sum-copies-sold inventory)))
;; => #'user/copies-thread
(def revenue-thread (Thread. #(sum-revenue inventory)))
;; => #'user/revenue-thread

But, how the fuck do I ensure that both of those threads complete.

  1. Lets say we want to run copies and revenue threads
  2. Then we want to wait till both of them complete
  3. Then we want to print the results

Steps 2 and 3 are accomplished by deref.

Try2 (success)

(let [copies-promise (promise)
      revenue-promise (promise)]
  (.start (Thread. #(deliver copies-promise (sum-copies-sold inventory))))
  (.start (Thread. #(deliver revenue-promise (sum-revenue inventory))))

  (str "The total number of books sold is " @copies-promise " and total revenue is " @revenue-promise)
  )
;; => "The total number of books sold is 68 and total revenue is 425"

The deref ensure that str wont start until both the threads complete.

A value with a Future

Firing off a computation in another thread, then getting its result back through a promise is a very common pattern. There is a prepackaged helper for this pattern: future

You can think of a future as a promise that comes with its own thread.

(def revenue-future (future (apply + (map :revenue inventory))))
;; => #'user/revenue-future

A future can be de-referenced using @

(identity @revenue-future)
;; => 425

Staying out of Trouble

Some problems with threads:-

Two threads may fight over a mutable resource

Threads are expensive. Creating them takes time. Once created, they take up a reasonable space in memory.


Prefer to use future over manually creating Thread. Because, future takes marking the space occupied by its internal thread for garbage collection.


Consider using the threadpool facility provided by java.util.concurrent.Executors

(import java.util.concurrent.Executors)
;; => java.util.concurrent.Executors
(def fixed-pool (Executors/newFixedThreadPool 3))
;; => #'user/fixed-pool

(defn a-lot-of-work []
  (println "Inside the function, a lot of work")
  (Thread/sleep 1000))
;; => #'user/a-lot-of-work
(defn even-more-work []
  (println "Inside the function, even more work")
  (Thread/sleep 1000))
;; => #'user/even-more-work

(.execute fixed-pool a-lot-of-work)
;; => nil
(.execute  fixed-pool even-more-work)
;; => nil

There are more specialized types of threadpools available.


A deref waits until some thread finishes. However, if the thread fails to return, then your program hangs forever. Therefore, always provide a timeout with a default value if time exceeded.

(def a-promise (promise))
;; => #'user/revenue-promise
(deref a-promise 500 :oh-snap)
;; => :oh-snap
(deliver a-promise "Ola")
;; => #<Promise@1746c7aa: "Ola">
(deref a-promise 500 :oh-snap)
;; => "Ola"

Problem: JVM will refuse to shutdown if there are background threads running:- Solution 1: Make sure all threads have finished before shutting down JVM, aka terminating the main thread. Solution 2: Mark your threads as daemons. Caution, Daemons will be terminated without warning whenever the main thread terminates.

In the wild

Compojure routing library usage. (Come back to this after reading the Web development with Clojure book) TODO: “The second thing to note is how this code, from top to bottom, is completely devoid of any Oh, gee, I’m working in a multithreaded application! special pleading.”

clojure.core/pmap is like map but runs each in a separate thread. There is a toy-my-pmap implementation which depicts using ~doall~ to stop the lazyness of ~map~ buitin

Wrapping up

nil

State

Since vectors are immutable, you cant just change the fifth element of some vector.

But, how to model immutable real world. Introducing atoms, refs and agents

Atom

Lets model a visitor counter to our webapp

(def counter (atom 0))
;; => #'user/counter
(defn greeting-message [req]
  (swap! counter inc)
  (if (= @counter 10)
    (str "Congrats! you are the " @counter " visitor!")
    (str "Welcome to Blotts Books!")))
;; => #'user/greeting-message
(greeting-message {})
;; => "Welcome to Blotts Books!"

Additional arguments to the state modification function can be passed, Eg:- Count each visit as 12 visits to cheat my VCs:- (swap! counter + 12)

swap! is thread safe:- After completing an update, it re-reads the old value of counter If, it has changed (due to another thread), then swap! will re-run. ie, We say that an atom is thread safe.

Note Although you have to deref the atom when, say, used as an argument to str You dont have to deref it when passed as an argument to swap! ie you must not do ~(swap! @counter + 12)~

Wrapping Map in an Atom

You can wrap any Clojure value in an atom.

(ns inventory)
;; => nil
(def by-title (atom {}))
;; => #'inventory/by-title
(defn add-book [{title :title :as book}]
  (swap! by-title #(assoc % title book)))
;; => #'inventory/add-book
(defn del-book [title]
  (swap! by-title #(dissoc % title)))
;; => #'inventory/del-book
(defn find-book [title]
  (get @by-title title))
;; => #'inventory/find-book

Lets use them

(add-book {:title "Emma" :author "Austen"})
;; => {"Emma" {:title "Emma", :author "Austen"}}
by-title
;; => #<Atom@4730704a: {"Emma" {:title "Emma", :author "Austen"}}>
(add-book {:title "Hackers&Painters" :author "PaulGraham"})
;; => {"Emma" {:title "Emma", :author "Austen"},
;;     "Hackers&Painters" {:title "Hackers&Painters", :author "PaulGraham"}}
(del-book "Hackers&Painters")
;; => {"Emma" {:title "Emma", :author "Austen"}}
(find-book "Emma")
;; => {:title "Emma", :author "Austen"}
(find-book "Bla")
;; => nil

Ref - a bunch of related atoms

Lets say we have two atoms:- total-num-of-books and inventory Whenever inventory mutates, we want to mutate total-num-of-books

We could use swap! twice. But, after the first swap!, our state is false until second swap! runs. What we really need is a way to mutate both the above atoms together. Enter ref

(ns inventory)
;; => nil
(def by-title (ref {}))
;; => #'inventory/by-title
(def total-copies (ref 0))
;; => #'inventory/total-copies

;; Each book is of the format {:title "Emma" :author "Austen" :copies 17}
;; by-title is an inventory where the key is the title, and the value is the above individual book-map
(defn add-book [{title :title :as book}]
  (dosync
    (alter by-title #(assoc % title book))
    (alter total-copies + (:copies book))))
;; => #'inventory/add-book

Like atom, ref is also thread-safe

Agent

What if we want to do a side effect whenever a mutable value changes ie our update function that we passed to swap! or alter needs to do one-side effect as it mutates

Atoms and Refs dont gurantee that the update function runs only once. (because of thread safe check and rerun logic)

Enter agent and send

send accepts the same arguments as swap! and alter

(ns inventory)
;; => nil
(def by-title (agent {}))
;; => #'inventory/by-title

(defn add-book [{title :title :as book}]
  (send by-title
    (fn [by-title-map]                  ;;we could have written this as a #(...) aka lambda as well
      (notify-inventory-change :add book) ;;this function has side effects, which we want to run guranteed-once.
      (assoc by-title-map title book))))

swap!, alter : are synchronous send is asynchronous

Which container to reach for to model some value?

Rules:-

  1. If the value is mostly stable, prefer a var with thread local changes, use dynamic var
  2. If there are side effects that need to happen whenever the value changes use agent
  3. If there are multiple values that need to update together use ref
  4. If there is a single value that mutates use atom Prefer using an atom over going for the more specialized mutable value containers.

In the wild

LightTable’s Clojure language plugin uses atom

Clojure’s builtin memoize implementation uses atom

Overtone:- Introduces ref-set, ensure

Overtone has a mind-bending example of agent. Come back to this later.

Staying out of trouble

(def title-atom (atom "Pride and Prejudice"))
;; => #'inventory/title-atom
(swap! title-atom #(str % " and Zombies"))
;; => "Pride and Prejudice and Zombies"
title-atom
;; => #<Atom@365d7870: "Pride and Prejudice and Zombies">

title-atom is pointing to a mutable value container This pointing doesnt change ?aka, title-atom itself hasnt been mutated.

But the value stored in the container changes


Some commentary on how exceptions are raised should an update fail, for atom, ref, agent For agent, the behaviour is different. (see book for details) Introduces agent-error, restart-agent functions


shutdown-agents

Wrapping up

nil

Read and Eval

In the chapter, we learn the reason behind Clojure’s lispy0 syntax.

Homoiconic

Does Clojure code syntax look a lot similar to its list data syntax. Its not a coincidence.

Just some data:

'(helvetica times-roman [comic-sans]
   (futura gil-sans
     (courier "All the fonts I have loved!")))

Still data, because of the quote

'(defn print-greeting [preferred-customer]
   (if preferred-customer
     (println "Welcome back!")))

Now its code

(defn print-greeting [preferred-customer blabla]
   (if preferred-customer
     (println "Welcome back!")))

Clojure code looks like Clojure data because, Clojure code is data Clojure uses the same syntax and datastructures; to represent code and data.

A Clojure function call (println "Hi") is an actual Clojure list. Function arguments [preferred-customer blabla] is an actual Clojure vector.

Languages where code is data are called homoiconic

Simple usage of read and eval

The machinery used by Clojure to read and evaluate code is surprisingly simple:- It consists of just two functions:-

  1. read - kinda boring function
  2. eval - very interesting function

(read) without arguments waits for input from standard input.

(read)
;; => "I_typed_this_into_stdin"

Let me call read, but type out an actual Clojure function:-

(defn print-greeting [preferred-customer]
  (if preferred-customer (println "Welcome back!")))
(read)
;; => (defn
;;     print-greeting
;;     [preferred-customer]
;;     (if preferred-customer (println "Welcome back!")))

What I get is an ordinary 4 element list.


read-string is a cousin of read, but accepts input from a string.

(def s "(defn print-greeting [preferred-customer]
          (if preferred-customer (println \"Welcome back!\")))")
;; => #'user/s

(read-string s)
;; => (defn
;;     print-greeting
;;     [preferred-customer]
;;     (if preferred-customer (println "Welcome back!")))

This also is an ordinary 4 element list. Nothing special.


eval takes the data you pass in, compiles and runs it as clojure code.

(def a-three-element-list '(+ 2 3))
;; => #'user/a-three-element-list
(eval a-three-element-list)
;; => 5

It eval can define functions too

(print-greeting true)
;; => Syntax error compiling at (Getting_Clojure.org::Read and Eval:87:1).
;;    Unable to resolve symbol: print-greeting in this context

(def some-list-data '(defn print-greeting [preferred-customer]
                       (if preferred-customer (println "Welcome back!"))))
;; => #'user/some-list-data

(eval some-list-data)
;; => #'user/print-greeting

;;Now the function is defined
(print-greeting true)                   ;does print to repl
;; => nil

Numbers, Strings, Keywords evaluate to themselves

(eval 55)
;; => 55
(eval "Hello")
;; => "Hello"
(eval :Hello)
;; => :Hello

What happens when you evaluate symbols:-

(def title "Alice in Wonderland")
;; => #'user/title

;;Get hold of its symbol
(def the-symbol 'title)
;; => #'user/the-symbol

(eval the-symbol)
;; => "Alice in Wonderland"

?So, eval-ing a symbol gives me the value associated with it.

What happens when you evaluate lists:-

(eval '(count "Anoop"))
;; => 5

So, eval-ing a list is equivalent to a function call. where first element of the list the function and the rest of the elements are the arguments.

Therefore, below is results in an error as 1 isnt a function

(eval '(1 2 3))
;; => Execution error (ClassCastException) at user/eval10394 (REPL:138).
;;    class java.lang.Long cannot be cast to class clojure.lang.IFn (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app')

So eval kinda runs stuff. But more importantly, it accepts ordinary clojure lists, vectors, keywords and symbols as its input One more example where this is more explicitly depicted:-

(def fn-name 'print-greeting)
;; => #'user/fn-name
(def args (vector 'preferred-customer))
;; => #'user/args
(def the-println (list 'println "Welcome back!"))
;; => #'user/the-println
(def body (list 'if 'preferred-customer the-println))
;; => #'user/body
(eval (list 'defn fn-name args body))
;; => #'user/print-greeting
(eval (list 'print-greeting true))      ;does print
;; => nil

Notice that there is no quotation in front of the-println when def-ing body

Homoiconic Advantage

Clojure’s syntax is the way it is because it balances between:-

  1. ease of representing code
  2. ease of representing data

Since code is data, reading source code is as easy as slurping ordinary clojure lists.

Eg:- Lets write a function that can read a file containing clojure code and turn it into a vector

(ns codetool.core
  (:require [clojure.java.io :as io]))
;; => nil
(defn read-source [path]
  (with-open [r (java.io.PushbackReader. (io/reader path))]
    (loop [result []]
      (let [expr (read r false :eof)]
        (if (= expr :eof)
          result
          (recur (conj result expr)))))))
;; => #'codetool.core/read-source

Lets create a test input file

(defn print-greeting [preferred-customer]
  (if preferred-customer (println "Welcome back, this time from file!")))
(read-source "./test.clj")
;; => [(defn
;;      print-greeting
;;      [preferred-customer]
;;      (if
;;       preferred-customer
;;       (println "Welcome back, this time from file!")))]

Notice that read slurped an entire sexp in a single read operation. In other words, the loop ran only once for my input.

Notice (read r false :eof) line shows how read accepts parameters.


Now we have all the tools needed to write our own REPL

(defn my-repl []
  (loop []
    (println (eval (read)))
    (recur)))

An eval of your own

Try 1.0

(defn my-eval [expr]
  (cond (string? expr) expr
        (keyword? expr) expr
        (number? expr) expr
        :else :completely-confused))

Try 2.0 Lets improve it to handle more cases

(declare eval-symbol)
;; => #'codetool.core/eval-symbol
(declare eval-vector)
;; => #'codetool.core/eval-vector
(declare eval-list)
;; => #'codetool.core/eval-list

(defn my-eval [expr]
  (cond (string? expr) expr
        (keyword? expr) expr
        (number? expr) expr
        (symbol? expr) (eval-symbol expr)
        (vector? expr) (eval-vector expr)
        (list? expr) (eval-list expr)
        :else :completely-confused))
;; => #'codetool.core/my-eval

For symbols, we can look them up in the current namespace

(defn eval-symbol [expr]
  (.get (ns-resolve *ns* expr)))

For vectors, we can recursively evaluate its elements

(defn eval-vector [expr]
  (vec (map my-eval expr)))
;; => #'codetool.core/eval-vector

For lists, my-eval must recurse and also invoke a final function call Recall that we did learn in previous section that eval treats lists as function call.

(defn eval-list [expr]
  (let [evaled-items (map my-eval expr)
        f (first evaled-items)
        args (rest evaled-items)]
    (apply f args)))
;; => #'user/eval-list

Building your own eval amounts to building your own lisp. I think even PG has his own version of eval in his Roots of Lisp essay.

In the wild

Look into doing MAL - Make a Lisp (skipped for now) (Maybe I can try building clojure using C)


Metadata is code that you type out. But, Metadata is actually just a map. ^:some_keyword is syntactic sugar for attaching metadata

(def books1 (with-meta ["Emma" "1984"] {:favorite-books true}))
;; => #'user/books1
(def books2 ^:favorite-books ["Emma" "1984"])
;; => #'user/books2

Fetch the metadata

(meta books1)
;; => {:favorite-books true}
(meta books2)
;; => {:favorite-books true}

The metadata that we typed out as code is a plain map:-

(def m (meta books1))
;; => #'user/m
(class m)
;; => clojure.lang.PersistentArrayMap
(count m)
;; => 1
(keys m)
;; => (:favorite-books)
(vals m)
;; => (true)

Aside-1: Equal values are still equal values even if they have different metadata

(def books3 (with-meta ["Emma" "1984"] {:hated-old-books true}))
;; => #'user/books3
(= books1 books3)
;; => true

Aside-2:- The function docstring is slurped into the metadata map of the function

Staying out of trouble

Dont ever use eval as a beginner.

  1. eval is inefficent, because it calls to the Clojure compile machinery during runtime
  2. eval has wierd corner-cases

Reserve eval for those 0.01% cases when you have no idea what runtime code you want to execute until some runtime data tells you.


Unlike ~eval~, ~read~ is frequently useful in Clojure programs you write

When reading data from untrusted source, use edn/read because ordinary read sometimes executes code while it reads.

(def s "#=(println \"Good Morning\")")
;; => #'user/s
(read-string s)                         ;also results in unexpected side effect, print out
;; => nil
s
;; => "#=(println \"Good Morning\")"

Fix:-

(require '[clojure.edn :as edn])
;; => nil
(def untrusted (edn/read))
;; => #'user/untrusted
untrusted
;; => "#=(println \"Good Morning\")"

The weird syntax benefits use because of:-

  1. ability to read code as data using read
  2. ability to evaluate data as code using eval
  3. macros

Wrapping up

nil

Macros

Macros help keep code concise, at the lowest levels of architecture.

Wont Functions suffice? Why Macros? Lets explore through an example

Eg:- A book has a rating between, say [-10, 10] We want to convert the rating into a string using a simple algorithm and print it.

(defn print-rating [rating]
  (cond (pos? rating) (println "Good book!")
        (zero? rating) (println "Totally indifferent.")
        :else (println "Run away!")))
;; => #'user/print-rating

Lets assume that we keep finding many such instances of three pronged cond-s in our codebase, all forking at rating How can we capture this repetitive pattern.


Try-1 (fail):- Through a function

(defn arithmetic-if [rating pos zero neg]
  (cond (pos? rating) pos
        (zero? rating) zero
        :else neg))
;; => #'user/arithmetic-if

(defn print-rating [rating]
  (arithmetic-if rating (println "Good book!") (println "Totally indifferent.") (println "Run away!")))
;; => #'user/print-rating

We immediately run into problems, above version prints all three arguments, for all inputs. This version of print-rating only when arguments to arithmetic-if dont have any side effects.

ie, above is equivalent to (arithmetic-if rating nil nil nil)

The rules of Clojure say that the arguments to a function will get evaluated first, and then the function body. (Look into SICP for more details on this.)


Try-2:- Through a function, and making its arguments functions too. So that the arguments wont get evaluated.

(defn arithmetic-if [rating pos-f zero-f neg-f]
  (cond (pos? rating) (pos-f)
        (zero? rating) (zero-f)
        :else (neg-f)))
;; => #'user/arithmetic-if
(defn print-rating [rating]
  (arithmetic-if rating #(println "Good book!") #(println "Totally indifferent.") #(println "Run away!")))
;; => #'user/print-rating

This works. But its kinda odd. Even to return a constant, you have to wrap it in a function.

Macros - built using code and without using backquote template

Some motivation for why macros are a distinct entity from functions

Lets look at what we are trying to accomplish from a different perspective:-

Desired syntax:- aka This is the starting form of the code:-

(arithmetic-if rating
  (println "Good book!")
  (println "Totally indifferent.")
  (println "Run away!"))

The final form of the code:-

(cond (pos? rating) (println "Good book!")
      (zero? rating) (println "Totally indifferent.")
      :else (println "Run away!"))

Lets ask ourselves the question, if form-A was data, what function would turn it into form-B:-

(defn arithmetic-if->cond [r pos zero neg]
  (list 'cond
        (list 'pos? r) pos
        (list 'zero? r) zero
        :else neg))
;; => #'user/arithmetic-if->cond

(arithmetic-if->cond 'rating
                     '(println "Good book!")
                     '(println "Totally indifferent.")
                     '(println "Run away!"))
;; => (cond
;;     (pos? rating)
;;     (println "Good book!")
;;     (zero? rating)
;;     (println "Totally indifferent.")
;;     :else
;;     (println "Run away!"))

Above works. We want some way of inserting the above function into the Clojure compiler. ie Just before compiling, run the above on some code. For this, we need compile time macros. (Aside doubt: how are they different from runtime macros)

Our first macro, doesnt use backquote template

(defmacro arithmetic-if [r pos zero neg]
  (list 'cond
        (list 'pos? r) pos
        (list 'zero? r) zero
        :else neg))
;; => #'user/arithmetic-if

Notice that the body of this macro and the above function are exactly the same. ie Macros are just functions, special only in when they run. They run as part of the compilation process, just before compilation, but after reading. xournalpp:when macros run.xopp

Usage

(arithmetic-if 10 :loved-it :meh :hated-it)
;; => :loved-it

When are macro arguments evaluated

(defmacro print-it [something]
  (list 'println "Something is" something))
;; => #'user/print-it
(print-it (+ 10 20))

Refer to above diagram:- The arithmetic expression (+ 10 20) did not get evaluated during read step. It did not get evaluated during macro-expansion step. It did get evaluated during runtime. Specifically, during list function’s runtime.

Macros built using backquote template, aka syntax quoting

Clojure code templating system involves:- backquote ` tilde ~~~ BTW, such templates are just plain clojure lists and vectors and maps.

(identity `(:my :first "template" :list 1 2 3))
;; => (:my :first "template" :list 1 2 3)
(def r 9)
;; => #'user/r
(def pos "It's positive!")
;; => #'user/pos
(def zero "It's zero!")
;; => #'user/zero
(def neg "It's negative")
;; => #'user/neg

(identity
  `(cond (pos? ~r) ~pos
         (zero? ~r) ~zero
         :else ~neg))
;; => (clojure.core/cond
;;     (clojure.core/pos? 9)
;;     "It's positive!"
;;     (clojure.core/zero? 9)
;;     "It's zero!"
;;     :else
;;     "It's negative")

This is so much more readable.

Notice that syntax quoting uses fully qualified symbols.

why? (Doubt)

?Because, at runtime, we may be in a different environment, than during the compile time. ie, is pos? the one defined in the context where the macro was defined (ie before read step) or is pos? the one defined in the context where the macro was called. (ie during ?)

Law of least surprises dictates that the macro writer intended to use the same pos? as the one that was available to him while writing that macro.


We have to fully qualify our symbols in macros because namespace aliases don’t exist to the reader.

Using syntax quoting to improve arithmetic-if

(defmacro arithmetic-if [r pos zero neg]
  `(cond (pos? ~r) ~pos
         (zero? ~r) ~zero
         :else ~neg))

In the wild

Although you may not be writing too many macros as a beginner, since macros are extensively used by others, you will definitely encounter them.

Clojure builtins when, cond, and, defn are all macros

The ~~@~ symbol represents unquote and splice

Lets try to define our own defn

(defmacro my-defn [sym args & body]
  `(def ~sym (fn ~args ~body)))
;; => #'user/my-defn

Try it

(my-defn hello [name]
  (println "Entered hello function")
  (str "Hello " name))
;; => #'user/hello

(hello "Anoop")
;; => Execution error (NullPointerException) at user/hello (REPL:211).
;;    Cannot invoke "clojure.lang.IFn.invoke(Object)" because the return value of "clojure.lang.IFn.invoke(Object)" is null

What is happening is body is ((println "Entered hello function") (str "Hello " name)) with an extra pair of parenthesis. We want to get rid of that parenthesis, and splice its contents while unquoting:- use ~~@~

(defmacro my-defn [sym args & body]
  `(def ~sym (fn ~args ~@body)))
;; => #'user/my-defn

(my-defn hello [name]
  (println "Entered hello function")
  (str "Hello " name))
;; => #'user/hello

(hello "Anoop")
;; => "Hello Anoop"

The hash symbol # for gensyms

Builtin and definition:-

(defmacro and
  ([] true)
  ([x] x)
  ([x & nxt]
   `(let [and# ~x]
      (if and# (and ~@next) and#))))

The hash symbol generates unique symbols ?to avoid namespace collisions :-

(identity `(and))
;; => (clojure.core/and)
(identity `(and#))
;; => (and__10087__auto__)

Still have some doubts. I need to find a better example that illustrates the need for #

Staying out of trouble

A nice example that illustrates what happens when in a macro

(defmacro illustrate-timing []
  (println "This happens at A? time")
  `(println "This happens at B? time"))
;; => #'user/illustrate-timing

(def foo-uses-that-macro #(illustrate-timing))
;; => #'user/foo-uses-that-macro

A=macro-expansion time B=runtime

ie Macros make themselve felt at two distinct times: directly at macro-expansion time, indirectly at runtime.

macroexpand-1

Since macros are only indirectly present at runtime, macro names are not visible in stacktraces.

(arithmetic-if 9
  (/ 1 0)
  (/ 1 0)
  (/ 1 0))
;; => Execution error (ArithmeticException) at user/eval10157 (REPL:291).
;;    Divide by zero

The stack trace:-

          Numbers.java:  190  clojure.lang.Numbers/divide
          Numbers.java: 3911  clojure.lang.Numbers/divide
                  REPL:  291  user/eval10157
                  REPL:  290  user/eval10157
         Compiler.java: 7194  clojure.lang.Compiler/eval
interruptible_eval.clj:  106  nrepl.middleware.interruptible-eval/evaluator/run/fn
interruptible_eval.clj:  101  nrepl.middleware.interruptible-eval/evaluator/run
           session.clj:  229  nrepl.middleware.session/session-exec/session-loop
    SessionThread.java:   21  nrepl.SessionThread/run

So macros are hard to debug. There is a helper to try out macros and debug them:-

(macroexpand-1 '(arithmetic-if 9
                  (/ 1 0)
                  (/ 1 0)
                  (/ 1 0)))
;; => (cond (pos? 9) (/ 1 0) (zero? 9) (/ 1 0) :else (/ 1 0))

You cant pass macros into a higher order function

Eg:-

(defmacro describe-it [it]
  `(let [value# ~it]
     (cond (list? value#) :a-list
           (vector? value#) :a-vector
           (number? value#) :a-number
           :else :no-idea)))
;; => #'user/describe-it

You cannot use it as an argument to map

(map describe-it [10 "Anup" [1 2 3]])
;; => Syntax error compiling at (Getting_Clojure.org::Macros:337:1).
;;    Can't take value of a macro: #'user/describe-it

The solution is simple. In such cases, most probably, a simple function suffices and you have over-reached for a macro. ie there is no reason why describe-it couldnt be a simple function.

Wrapping up

First preference: Try to solve a problem with ordinary function Second preference: You are writing a lot of repetitive code, go for a macro Third preference (rare 0.01% case): Turn data directly into code, go for eval from previous chapter.

Conclusion

nil

File Settings

It is important to ensure that the local variables are defined near the end of a file. Specifically, emacs loads local variables by reading only the last 3000 characters of the file being read.

About

a comprehensive, hands-on study of the Clojure programming language, documenting all concepts and implementing code examples in a detailed Literate Programming notebook

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published