23252826a0
- New chapter 05-clojure-nim.md (EN + BG) covering: - Native compilation pipeline (Clojure → Nim → C → binary) - AI-powered development (error explanation, code generation) - JSON REPL for AI agents - loop/recur with real TCO - Cross-compilation: JS, shared libs, WASM - Persistent data structures (HAMT) - Concurrency: atoms, agents, channels - Updated book README.md with Clojure/Nim focus - Added Clojure/Nim terms to subject indices (EN + BG) - Removed books/ from .gitignore so it can be pushed to GitLab
711 lines
13 KiB
Markdown
711 lines
13 KiB
Markdown
# Pure Clojure: Advanced Topics
|
|
|
|
## Table of Contents
|
|
|
|
1. [Advanced Functions](#1-advanced-functions)
|
|
2. [Lazy Sequences Deep Dive](#2-lazy-sequences-deep-dive)
|
|
3. [Transducers](#3-transducers)
|
|
4. [Specs and Validation](#4-specs-and-validation)
|
|
5. [The Collection Protocol](#5-the-collection-protocol)
|
|
6. [Reducibles](#6-reducibles)
|
|
7. [Parallelism](#7-parallelism)
|
|
8. [Performance Optimization](#8-performance-optimization)
|
|
9. [Index](#9-index)
|
|
|
|
---
|
|
|
|
## 1. Advanced Functions
|
|
|
|
### 1.1 Variadic Functions
|
|
|
|
Functions can accept variable numbers of arguments:
|
|
|
|
```clojure
|
|
(defn print-all [& args]
|
|
(doseq [arg args]
|
|
(println arg)))
|
|
|
|
(print-all "a" "b" "c")
|
|
|
|
;; With required arguments
|
|
(defn greet [name & greeting-parts]
|
|
(str (clojure.string/join " " greeting-parts) ", " name "!"))
|
|
|
|
(greet "World" "Hello" "Good morning") ;; => "Hello Good morning, World!"
|
|
```
|
|
|
|
### 1.2 Rest Parameters in Detail
|
|
|
|
The `&` symbol captures remaining arguments as a sequence:
|
|
|
|
```clojure
|
|
(defn my-apply [f & args]
|
|
(apply f args))
|
|
|
|
;; Using with destructuring
|
|
(defn first-two [[a b & rest]]
|
|
{:first a :second b :rest rest})
|
|
|
|
(first-two [1 2 3 4 5])
|
|
;; => {:first 1 :second 2 :rest (3 4 5)}
|
|
```
|
|
|
|
### 1.3 Keyword Arguments
|
|
|
|
Clojure supports keyword arguments via destructuring:
|
|
|
|
```clojure
|
|
(defn configure [name & {:keys [debug verbose output]
|
|
:or {debug false verbose false output "stdout"}}]
|
|
{:name name :debug debug :verbose verbose :output output})
|
|
|
|
(configure "test" :debug true :verbose true :output "file.txt")
|
|
;; => {:name "test" :debug true :verbose true :output "file.txt"}
|
|
```
|
|
|
|
### 1.4 Mutual Recursion
|
|
|
|
Functions can call each other:
|
|
|
|
```clojure
|
|
(defn even? [n]
|
|
(if (zero? n)
|
|
true
|
|
(odd? (dec n))))
|
|
|
|
(defn odd? [n]
|
|
(if (zero? n)
|
|
false
|
|
(even? (dec n))))
|
|
|
|
(even? 4) ;; => true
|
|
(odd? 3) ;; => true
|
|
```
|
|
|
|
### 1.5 Memoization
|
|
|
|
Cache function results:
|
|
|
|
```clojure
|
|
(defn slow-fib [n]
|
|
(if (<= n 1)
|
|
n
|
|
(+ (slow-fib (- n 1))
|
|
(slow-fib (- n 2)))))
|
|
|
|
(def memo-fib (memoize slow-fib))
|
|
|
|
;; Time difference is dramatic for larger n
|
|
(time (memo-fib 35)) ;; Much faster
|
|
```
|
|
|
|
### 1.6 Preconditions and Postconditions
|
|
|
|
Validate inputs and outputs:
|
|
|
|
```clojure
|
|
(defn absolute-value [n]
|
|
{:pre [(number? n)]
|
|
:post [(number? %)
|
|
(>= % 0)]}
|
|
(if (neg? n)
|
|
(- n)
|
|
n))
|
|
|
|
(defn divide [a b]
|
|
{:pre [(not (zero? b)) "Divisor cannot be zero"]}
|
|
(/ a b))
|
|
```
|
|
|
|
### 1.7 Function Metadata
|
|
|
|
Functions can have metadata:
|
|
|
|
```clojure
|
|
(defn ^:private internal-helper [x]
|
|
x)
|
|
|
|
(defn ^:deprecated old-function [x]
|
|
x)
|
|
|
|
;; Check metadata
|
|
(meta #'internal-helper)
|
|
;; => {:private true, ...}
|
|
```
|
|
|
|
### 1.8 Arities and Overloading
|
|
|
|
```clojure
|
|
(defn arity-error []
|
|
(throw (ex-info "Invalid arity" {})))
|
|
|
|
(defn complete
|
|
([x] (complete x 1))
|
|
([x y] (+ x y))
|
|
([x y z] (+ x y z)))
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Lazy Sequences Deep Dive
|
|
|
|
### 2.1 Realizing Sequences
|
|
|
|
Lazy sequences are realized (evaluated) as needed:
|
|
|
|
```clojure
|
|
(def lazy-nats (range)) ;; Infinite
|
|
|
|
(take 10 lazy-nats) ;; Realizes first 10
|
|
|
|
;; Force full realization
|
|
(doall lazy-nats) ;; Danger: infinite!
|
|
(doall (take 1000 lazy-nats))
|
|
```
|
|
|
|
### 2.2 Chunked Sequences
|
|
|
|
Clojure's lazy sequences are chunked (typically 32 elements):
|
|
|
|
```clojure
|
|
;; Range creates chunked sequences
|
|
(class (range 100)) ;; => clojure.lang.LongRange
|
|
|
|
;; Each chunk is realized at once
|
|
```
|
|
|
|
### 2.3 Lazy Cons and Realization
|
|
|
|
```clojure
|
|
;; cons creates a lazy sequence
|
|
(def custom-seq (cons 1 (lazy-seq (cons 2 ()))))
|
|
|
|
;; lazy-seq defers computation
|
|
(defn fibs []
|
|
(cons 0
|
|
(cons 1
|
|
(map + (fibs) (rest (fibs))))))
|
|
```
|
|
|
|
### 2.4 Seqable Objects
|
|
|
|
Any object can be made sequential by implementing the `seq` method:
|
|
|
|
```clojure
|
|
(extend-type String
|
|
clojure.core.protocols/Coll
|
|
(coll [s] (seq s)))
|
|
|
|
;; Now strings work with sequence functions
|
|
(map clojure.string/upper-case "hello")
|
|
;; => (\H \E \L \L \O)
|
|
```
|
|
|
|
### 2.5 Infinite Sequences
|
|
|
|
```clojure
|
|
;; Repeated cycle
|
|
(def repeating (cycle [:a :b :c]))
|
|
|
|
;; Repeat forever
|
|
(def ones (repeatedly 1))
|
|
(def randoms (repeatedly #(rand-int 100)))
|
|
|
|
;; Iterate - apply function to previous result
|
|
(def powers-of-two (iterate #(* 2 %) 1))
|
|
(def collatz (iterate #(if (even? %) (/ % 2) (inc (* 3 %))) 1))
|
|
```
|
|
|
|
### 2.6 Sequence Performance
|
|
|
|
```clojure
|
|
;; Don't hold onto head of lazy sequence
|
|
(defn bad-sum []
|
|
(let [large-seq (range 10000000)]
|
|
(reduce + (take 10 large-seq)))) ;; Holds reference to entire seq
|
|
|
|
(defn good-sum []
|
|
(reduce + (take 10 (range 10000000)))) ;; Head can be GC'd
|
|
```
|
|
|
|
### 2.7 Eager vs Lazy
|
|
|
|
```clojure
|
|
;; mapcat can be eager
|
|
(mapcat reverse [[1 2] [3 4]]) ;; => (2 1 4 3)
|
|
|
|
;; into forces realization
|
|
(into [] (map inc (range 1000)))
|
|
|
|
;;顽固 (into) is efficient - doesn't create intermediate collections
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Transducers
|
|
|
|
Transducers are composable, lazy transformations independent of input context.
|
|
|
|
### 3.1 Creating Transducers
|
|
|
|
```clojure
|
|
;; Without context
|
|
(def increment (map inc))
|
|
(def only-evens (filter even?))
|
|
|
|
;; Composing transducers
|
|
(def transform (comp
|
|
(filter even?)
|
|
(map inc)
|
|
(take 10)))
|
|
```
|
|
|
|
### 3.2 Using Transducers
|
|
|
|
```clojure
|
|
;; With any sequence-like collection
|
|
(transduce transform + (range 100))
|
|
;; => Sum of first 10 even numbers + 1
|
|
|
|
(into [] transform (range 100))
|
|
;; => [3 5 7 9 11 13 15 17 19 21]
|
|
|
|
(sequence transform (range 100))
|
|
;; => Returns lazy sequence
|
|
```
|
|
|
|
### 3.3 Completing Reductions
|
|
|
|
Some transducers need to do something at the end:
|
|
|
|
```clojure
|
|
(def taking-transform
|
|
(fn [rf]
|
|
(let [n (volatile! 5)]
|
|
(fn
|
|
([] (rf))
|
|
([result] (rf result))
|
|
([result input]
|
|
(if (pos? @n)
|
|
(do (vswap! n dec)
|
|
(rf result input))
|
|
(reduced result)))))))
|
|
|
|
(transduce taking-transform + (range 100)) ;; => 10
|
|
```
|
|
|
|
### 3.4 Early Termination
|
|
|
|
```clojure
|
|
;; reduced wraps a value to stop early
|
|
(transduce (filter odd?) + (range 10))
|
|
;; => 25 (1+3+5+7+9)
|
|
|
|
;; Use reduced? to check
|
|
(reduced? (reduced 5)) ;; => true
|
|
```
|
|
|
|
### 3.5 Cat and Completing
|
|
|
|
```clojure
|
|
(require '[clojure.core.protocols :as p])
|
|
|
|
;; The completing arity of the reducing function
|
|
(transduce
|
|
(map inc)
|
|
(fn
|
|
([result] result) ;; completing arity
|
|
([result input] (rf result input)))
|
|
[]
|
|
(range 5))
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Specs and Validation
|
|
|
|
### 4.1 Introduction to Spec
|
|
|
|
Spec provides runtime validation and generative testing (via `clojure.spec.gen`).
|
|
|
|
### 4.2 Defining Specs
|
|
|
|
```clojure
|
|
(require '[clojure.spec.alpha :as s])
|
|
|
|
(s/def ::name string?)
|
|
(s/def ::age (s/and int? #(>= % 0)))
|
|
(s/def ::person (s/keys :req [::name ::age]))
|
|
```
|
|
|
|
### 4.3 Conforming
|
|
|
|
```clojure
|
|
(s/conform ::age 25) ;; => 25
|
|
(s/conform ::age -5) ;; => :clojure.spec.alpha/invalid
|
|
|
|
(s/conform ::person {::name "John" ::age 30})
|
|
;; => {::name "John" ::age 30}
|
|
```
|
|
|
|
### 4.4 Validation with `valid?`
|
|
|
|
```clojure
|
|
(s/valid? ::age 25) ;; => true
|
|
(s/valid? ::age -5) ;; => false
|
|
(s/valid? ::person {::name "John" ::age 30}) ;; => true
|
|
```
|
|
|
|
### 4.5 Generative Testing
|
|
|
|
```clojure
|
|
(require '[clojure.spec.gen.alpha :as gen])
|
|
|
|
;; Generate values
|
|
(gen/generate (s/gen ::age))
|
|
(gen/sample (s/gen ::age))
|
|
|
|
;; Test with spec
|
|
(s/def ::email (s/and string?
|
|
#(re-find #"@" %)))
|
|
|
|
(s/fdef greet
|
|
:args (s/cat :name ::name)
|
|
:ret string?)
|
|
|
|
;; Run generative tests
|
|
(stest/instrument `greet)
|
|
```
|
|
|
|
### 4.6 Multi-spec
|
|
|
|
```clojure
|
|
(s/def ::shape (s/multi-spec :type keyword?))
|
|
|
|
(defmethod shape-spec :circle [_]
|
|
(s/keys :req [:radius]))
|
|
|
|
(defmethod shape-spec :rect [_]
|
|
(s/keys :req [:width :height]))
|
|
```
|
|
|
|
---
|
|
|
|
## 5. The Collection Protocol
|
|
|
|
### 5.1 Collection Hierarchy
|
|
|
|
```
|
|
IPersistentCollection
|
|
IPersistentList
|
|
IPersistentVector
|
|
IPersistentMap
|
|
IPersistentSet
|
|
```
|
|
|
|
### 5.2 Key Protocols
|
|
|
|
```clojure
|
|
;; Sequential
|
|
(first coll)
|
|
(rest coll)
|
|
(next coll)
|
|
(cons item coll)
|
|
|
|
;; Counted
|
|
(count coll)
|
|
|
|
;; Indexed (Vectors)
|
|
(nth coll index)
|
|
(get coll index)
|
|
|
|
;; Associative (Maps)
|
|
(assoc coll key val)
|
|
(dissoc coll key)
|
|
(find coll key)
|
|
(keys coll)
|
|
(vals coll)
|
|
```
|
|
|
|
### 5.3 Extending Collections
|
|
|
|
```clojure
|
|
;; Using reify
|
|
(def my-collection
|
|
(reify
|
|
clojure.core.protocols/Coll
|
|
(coll [this] this)
|
|
clojure.core.protocols/Indexed
|
|
(nth [this i] (get [10 20 30] i))))
|
|
|
|
(nth my-collection 1) ;; => 20
|
|
```
|
|
|
|
### 5.4 Custom Reducibles
|
|
|
|
```clojure
|
|
(defrecord Range [start end]
|
|
clojure.core.protocols/Coll
|
|
(coll [this] (seq (range start end)))
|
|
|
|
(reduce + (Range. 1 10)) ;; => 45
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Reducibles
|
|
|
|
Reducers provide a way to perform parallel reductions without lazy sequences.
|
|
|
|
### 6.1 Using Reducers
|
|
|
|
```clojure
|
|
(require '[clojure.core.reducers :as r])
|
|
|
|
;; Parallel map (parallelizes automatically in fold)
|
|
(r/map inc (range 1000))
|
|
|
|
;; fold uses parallel reduction
|
|
(r/fold + (r/map inc (range 1000000)))
|
|
```
|
|
|
|
### 6.2 Custom Reducers
|
|
|
|
```clojure
|
|
;; fold requires a foldable coll and combining function
|
|
(r/fold
|
|
(fn ([] 0) ([x y] (+ x y)))
|
|
(fn ([x] x) ([x y] (+ x y)))
|
|
(range 1000))
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Parallelism
|
|
|
|
### 7.1 pmap
|
|
|
|
Parallel map (lazy):
|
|
|
|
```clojure
|
|
;; Like map but evaluates in parallel
|
|
(time
|
|
(doall (pmap #(do (Thread/sleep 100) %) (range 10))))
|
|
;; Much faster than regular map with blocking operations
|
|
```
|
|
|
|
### 7.2 Reducers for Parallelism
|
|
|
|
```clojure
|
|
;; Folding with multiple cores
|
|
(r/fold 100 + (range 10000000))
|
|
|
|
;; Custom combiner
|
|
(r/fold
|
|
100
|
|
(fn ([] 0) ([a b] (+ a b)))
|
|
(fn ([] 0) ([a b] (+ a b)))
|
|
(range 10000000))
|
|
```
|
|
|
|
### 7.3 Futures
|
|
|
|
```clojure
|
|
;; Independent parallel tasks
|
|
(let [a (future (compute-a))
|
|
b (future (compute-b))]
|
|
[@a @b]) ;; Waits for both
|
|
```
|
|
|
|
### 7.4 CompletableFuture (via Java interop - noted only)
|
|
|
|
Note: Java's `CompletableFuture` requires Java interop. Pure Clojure alternatives include:
|
|
- Core.async channels
|
|
- Manifold library
|
|
- Promises with futures
|
|
|
|
---
|
|
|
|
## 8. Performance Optimization
|
|
|
|
### 8.1 Persistent Data Structures
|
|
|
|
Clojure's persistent data structures share structure:
|
|
|
|
```clojure
|
|
;; Adding to vector shares most structure
|
|
(def v1 [1 2 3 4 5])
|
|
(def v2 (conj v1 6))
|
|
|
|
;; v1 and v2 share [1 2 3 4 5]
|
|
;; Only new nodes created for path to new element
|
|
```
|
|
|
|
### 8.2 Transient Data Structures
|
|
|
|
For local, temporary mutations:
|
|
|
|
```clojure
|
|
(defn slow-accumulation []
|
|
(loop [coll []
|
|
i 0]
|
|
(if (= i 100000)
|
|
coll
|
|
(recur (conj coll i) (inc i)))))
|
|
|
|
(defn fast-accumulation []
|
|
(persistent!
|
|
(loop [coll (transient [])
|
|
i 0]
|
|
(if (= i 100000)
|
|
coll
|
|
(recur (conj! coll i) (inc i))))))
|
|
|
|
(time (count (slow-accumulation))) ;; Slower
|
|
(time (count (fast-accumulation))) ;; Faster
|
|
```
|
|
|
|
### 8.3 Chunked Operations
|
|
|
|
```clojure
|
|
;; Prefer chunked operations
|
|
(into [] (map inc (range 1000))) ;; Creates one intermediate seq
|
|
(into [] (mapcat list (range 100))) ;; Flattens lazily
|
|
```
|
|
|
|
### 8.4 Keep Args Eager
|
|
|
|
```clojure
|
|
;; Bad: holds head of sequence
|
|
(def bad-result (map f large-collection))
|
|
|
|
;; Good: process immediately
|
|
(into [] (map f large-collection))
|
|
```
|
|
|
|
### 8.5 Batch Processing
|
|
|
|
```clojure
|
|
;; Instead of many small operations
|
|
(doseq [x items]
|
|
(update-db x))
|
|
|
|
;; Consider batching
|
|
(batch-update items)
|
|
```
|
|
|
|
### 8.6 Preload and Cache
|
|
|
|
```clojure
|
|
;; Memoization for expensive computations
|
|
(def cached expensive-lookup
|
|
(memoize (fn [k]
|
|
(compute-expensively k))))
|
|
|
|
;; Preload on startup
|
|
(def initialized-data
|
|
(delay (load-and-process-data)))
|
|
```
|
|
|
|
### 8.7 Bencharking
|
|
|
|
```clojure
|
|
(require '[criterium.core :as c])
|
|
|
|
(c/quick-bench (reduce + (range 10000)))
|
|
;; Reports mean, std deviation, etc.
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Index
|
|
|
|
### A
|
|
|
|
- `arity` - [1.8](#18-arities-and-overloading)
|
|
- `assert` - [1.6](#16-preconditions-and-postconditions)
|
|
|
|
### C
|
|
|
|
- `chunked-seq?` - [2.2](#22-chunked-sequences)
|
|
- `coll` - [5.3](#53-extending-collections)
|
|
- `complement` - [1.3](#13-keyword-arguments)
|
|
- `comp` - [1.3](#13-keyword-arguments)
|
|
|
|
### D
|
|
|
|
- `delay` - [2.6](#26-sequence-performance)
|
|
- `delayed?` - [2.6](#26-sequence-performance)
|
|
- `deref` - [2.6](#26-sequence-performance)
|
|
|
|
### F
|
|
|
|
- `force` - [2.6](#26-sequence-performance)
|
|
- `fnil` - [1.3](#13-keyword-arguments)
|
|
- `fold` - [6.2](#62-using-reducers)
|
|
- `fpartial` - [1.3](#13-keyword-arguments)
|
|
|
|
### G
|
|
|
|
- `gen` - [4.5](#45-generative-testing)
|
|
- `generate` - [4.5](#45-generative-testing)
|
|
|
|
### I
|
|
|
|
- `into` - [3.2](#32-using-transducers)
|
|
- `iterate` - [2.5](#25-infinite-sequences)
|
|
|
|
### L
|
|
|
|
- `lazy-cat` - [2.3](#23-lazy-cons-and-realization)
|
|
- `lazy-seq` - [2.3](#23-lazy-cons-and-realization)
|
|
- `let` - [1.2](#12-rest-parameters-in-detail)
|
|
|
|
### M
|
|
|
|
- `memoize` - [1.5](#15-memoization)
|
|
- `multi-spec` - [4.6](#46-multi-spec)
|
|
- `mmerge` - [6.1](#61-using-reducers)
|
|
|
|
### N
|
|
|
|
- `nested` - [5.3](#53-extending-collections)
|
|
- `next` - [5.2](#52-key-protocols)
|
|
|
|
### P
|
|
|
|
- `parallelize` - [7.2](#72-reducers-for-parallelism)
|
|
- `partial` - [1.3](#13-keyword-arguments)
|
|
- `pmap` - [7.1](#71-pmap)
|
|
- `promote` - [6.2](#62-using-reducers)
|
|
|
|
### R
|
|
|
|
- `realized?` - [2.1](#21-realizing-sequences)
|
|
- `reduced` - [3.4](#34-early-termination)
|
|
- `reduced?` - [3.4](#34-early-termination)
|
|
- `reductions` - [3.3](#33-completing-reductions)
|
|
|
|
### S
|
|
|
|
- `sample` - [4.5](#45-generative-testing)
|
|
- `sequence` - [3.2](#32-using-transducers)
|
|
- `spec` - [4.1](#41-introduction-to-spec)
|
|
- `split-with` - [2.6](#26-sequence-performance)
|
|
|
|
### T
|
|
|
|
- `test` - [4.5](#45-generative-testing)
|
|
- `transduce` - [3.2](#32-using-transducers)
|
|
- `transient` - [8.2](#82-transient-data-structures)
|
|
- `tree-seq` - [2.6](#26-sequence-performance)
|
|
|
|
### V
|
|
|
|
- `volatile!` - [1.7](#17-function-metadata)
|
|
- `volatile?` - [1.7](#17-function-metadata)
|
|
|
|
---
|
|
|
|
*Pure Clojure: Advanced Topics*
|