Skip to content

A collection of utilities that improve Clojure experience

License

Notifications You must be signed in to change notification settings

tonsky/clojure-plus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Clojure Plus

A collection of utilities that improve Clojure experience.

Using

Add this to deps.edn:

io.github.tonsky/clojure-plus {:mvn/version "1.0.0"}

Functions

if+

Allows sharing local variables between condition and then clause.

Use :let [...] form (not nested!) inside and condition and its bindings will be visible in later and clauses and inside then branch:

(if+ (and
       (= 1 2)
       ;; same :let syntax as in doseq/for
       :let [x 3
             y (+ x 4)]
       ;; x and y visible downstream
       (> y x))
  
  ;; “then” branch: x and y visible here!
  (+ x y 5)
  
  ;; “else” branch: no x nor y
  6)

Note: :let-declared vars are only visible inside then branch because they ultimately depend on all conditions to be true. That lets you do things like:

(if+ (and
       (some? x)
       :let [f (.-f x)]
       (= :... f))
  ...)

This will be quite inconvenient to solve using normal if and let, as it would require you to duplicate <else> clause:

(if (some? x)
  (let [f (.-f x)]
    (if (= :... f)
      <then>
      <else>))
  <else>)

Another asymmetry is and vs or: :let only works insid top-level and condition.

when+

Same as if+, but wraps body in implicit do:

(when+ (and
         (= 1 2)
         ;; same :let syntax as in doseq/for
         :let [x 3
               y (+ x 4)]
         ;; x and y visible downstream
         (> y x))
  
  ;; body: x and y visible here!
  (+ x y 5))

cond+

Cond on steroids.

Define new variables between conditions:

(cond+
  false   :false
  :let    [x 1]
  (= 1 x) (str x)) ; => \"1\"

Insert imperative code:

(cond+
  (= 1 a) :false
  :do     (println a) ; will print 1 before continuing evaluating
  :else   :true)

Declare variables inside conditions, just like if+:

(cond+
  (and
    (= 1 1)
    ;; declare new vars inside `and` condition
    :let [x 2
          y (+ x 1)]
    ;; use them downstream in the same condition
    (> y x))
  ;; matching branch sees them too
  [x y]) ;; => [2 3]

clojure+.walk

A drop-in replacement for clojure.walk that does not recreate data structures if they didn’t change (result of transform funcion is identical?)

Normally, clojure.walk will create new map from scratch and copy :a 1, :b 2 to it:

(let [m {:a 1, :b 2}]
  (identical? m (clojure.walk/postwalk identity m)))

;; (into (empty m) (map identity m))
; => false

When the structure is deep and everything is recreated, it can be very taxing on GC.

Compare it to:

(let [m {:a 1, :b 2}]
  (identical? m (clojure+.walk/postwalk identity m)))

; => true

clojure+.walk reuses existing maps and updates keys that has changed. This works significantly better for transforms that don’t touch some parts of the tree at all, but also utilizes structural sharing for cases when you only update few keys.

License

Copyright © 2025 Nikita Prokopov

Licensed under MIT.