feat: add Clojure/Nim chapter to the book + restructure for GitLab
- 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
This commit is contained in:
+1
-1
@@ -14,4 +14,4 @@ test_deps
|
|||||||
test_eval
|
test_eval
|
||||||
test_macros
|
test_macros
|
||||||
test_ai_assist
|
test_ai_assist
|
||||||
books/
|
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Clojure/Nim — The Native Clojure Book
|
||||||
|
|
||||||
|
> A practical guide to Clojure that compiles to native binaries. No JVM required.
|
||||||
|
|
||||||
|
## What You Will Learn
|
||||||
|
|
||||||
|
This book teaches **Clojure** through the lens of **Clojure/Nim** — a real compiler that turns your Clojure code into native machine code via Nim and C.
|
||||||
|
|
||||||
|
You do not need Java. You do not need Leiningen. You need Nim, a C compiler, and curiosity.
|
||||||
|
|
||||||
|
## Book Structure
|
||||||
|
|
||||||
|
| Chapter | Topic | Why It Matters |
|
||||||
|
|---------|-------|----------------|
|
||||||
|
| [01 — Fundamentals](en/01-fundamentals.md) | Syntax, data structures, functions, control flow | The foundation. Applies to all Clojure dialects. |
|
||||||
|
| [02 — Advanced](en/02-advanced.md) | Transducers, specs, parallelism, performance | Write code that scales. |
|
||||||
|
| [03 — Tooling](en/03-tooling.md) | Project structure, deps, testing, debugging | Ship reliable software. |
|
||||||
|
| [04 — Recipes](en/04-recipes.md) | Common patterns, state management, APIs | Copy-paste solutions that work. |
|
||||||
|
| **[05 — Clojure/Nim](en/05-clojure-nim.md)** | **Native compilation, AI integration, JSON REPL, WASM** | **What makes this dialect unique.** |
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitlab.com/balvatar/lisp-nim.git
|
||||||
|
cd lisp-nim
|
||||||
|
make build && make check
|
||||||
|
./cljnim run examples/hello.clj
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the Examples
|
||||||
|
|
||||||
|
Most examples work in any Clojure REPL. Chapter 5 examples are specific to Clojure/Nim:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Human REPL
|
||||||
|
./cljnim repl
|
||||||
|
|
||||||
|
# JSON REPL (for AI agents)
|
||||||
|
./cljnim repl --json
|
||||||
|
|
||||||
|
# Compile to native binary
|
||||||
|
./cljnim run myprogram.clj
|
||||||
|
|
||||||
|
# Generate code with AI
|
||||||
|
./cljnim ai "function that reverses a list"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Language Versions
|
||||||
|
|
||||||
|
- **[English](en/)**
|
||||||
|
- **[Български](bg/)**
|
||||||
|
|
||||||
|
## Stats
|
||||||
|
|
||||||
|
- **8 chapters** (4 core + 4 translated)
|
||||||
|
- **1 new chapter** on Clojure/Nim (native compilation, AI, cross-targets)
|
||||||
|
- **All code tested** against Clojure/Nim v0.1.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This book is maintained as part of the Clojure/Nim project.*
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,710 @@
|
|||||||
|
# Чист Clojure: Разширени теми
|
||||||
|
|
||||||
|
## Съдържание
|
||||||
|
|
||||||
|
1. [Разширени функции](#1-разширени-функции)
|
||||||
|
2. [Мързеливи серии - задълбочено](#2-мързеливи-серии---задълбочено)
|
||||||
|
3. [Трансдюсъри](#3-трансдюсъри)
|
||||||
|
4. [Specs и валидация](#4-specs-и-валидация)
|
||||||
|
5. [Протоколът Collection](#5-протоколът-collection)
|
||||||
|
6. [Reducibles](#6-reducibles)
|
||||||
|
7. [Паралелизъм](#7-паралелизъм)
|
||||||
|
8. [Оптимизация на производителността](#8-оптимизация-на-производителността)
|
||||||
|
9. [Индекс](#9-индекс)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Разширени функции
|
||||||
|
|
||||||
|
### 1.1 Вариадични функции
|
||||||
|
|
||||||
|
Функциите могат да приемат променлив брой аргументи:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn print-all [& args]
|
||||||
|
(doseq [arg args]
|
||||||
|
(println arg)))
|
||||||
|
|
||||||
|
(print-all "a" "b" "c")
|
||||||
|
|
||||||
|
;; С задължителни аргументи
|
||||||
|
(defn greet [name & greeting-parts]
|
||||||
|
(str (clojure.string/join " " greeting-parts) ", " name "!"))
|
||||||
|
|
||||||
|
(greet "World" "Hello" "Good morning") ;; => "Hello Good morning, World!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Rest параметри в детайли
|
||||||
|
|
||||||
|
Символът `&` улавя останалите аргументи като серия:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn my-apply [f & args]
|
||||||
|
(apply f args))
|
||||||
|
|
||||||
|
;; Използване с деструктуриране
|
||||||
|
(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 Аргументи от тип ключова дума
|
||||||
|
|
||||||
|
Clojure поддържа аргументи от тип ключова дума чрез деструктуриране:
|
||||||
|
|
||||||
|
```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 Взаимна рекурсия
|
||||||
|
|
||||||
|
Функциите могат да се извикват една друга:
|
||||||
|
|
||||||
|
```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 Мемоизация
|
||||||
|
|
||||||
|
Кеширане на резултати от функции:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn slow-fib [n]
|
||||||
|
(if (<= n 1)
|
||||||
|
n
|
||||||
|
(+ (slow-fib (- n 1))
|
||||||
|
(slow-fib (- n 2)))))
|
||||||
|
|
||||||
|
(def memo-fib (memoize slow-fib))
|
||||||
|
|
||||||
|
;; Разликата във времето е драматична за по-големи n
|
||||||
|
(time (memo-fib 35)) ;; Много по-бързо
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.6 Пред- и пост-условия
|
||||||
|
|
||||||
|
Валидиране на входове и изходи:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn absolute-value [n]
|
||||||
|
{:pre [(number? n)]
|
||||||
|
:post [(number? %)
|
||||||
|
(>= % 0)]}
|
||||||
|
(if (neg? n)
|
||||||
|
(- n)
|
||||||
|
n))
|
||||||
|
|
||||||
|
(defn divide [a b]
|
||||||
|
{:pre [(not (zero? b)) "Делителят не може да е нула"]}
|
||||||
|
(/ a b))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.7 Метаданни на функции
|
||||||
|
|
||||||
|
Функциите могат да имат метаданни:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn ^:private internal-helper [x]
|
||||||
|
x)
|
||||||
|
|
||||||
|
(defn ^:deprecated old-function [x]
|
||||||
|
x)
|
||||||
|
|
||||||
|
;; Проверете метаданните
|
||||||
|
(meta #'internal-helper)
|
||||||
|
;; => {:private true, ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.8 Арности и претоварване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn arity-error []
|
||||||
|
(throw (ex-info "Невалидна арност" {})))
|
||||||
|
|
||||||
|
(defn complete
|
||||||
|
([x] (complete x 1))
|
||||||
|
([x y] (+ x y))
|
||||||
|
([x y z] (+ x y z)))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Мързеливи серии - задълбочено
|
||||||
|
|
||||||
|
### 2.1 Реализиране на серии
|
||||||
|
|
||||||
|
Мързеливите серии се реализират (оценяват) при необходимост:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def lazy-nats (range)) ;; Безкрайни
|
||||||
|
|
||||||
|
(take 10 lazy-nats) ;; Реализира първите 10
|
||||||
|
|
||||||
|
;; Принудете пълна реализация
|
||||||
|
(doall lazy-nats) ;; Опасно: безкрайна!
|
||||||
|
(doall (take 1000 lazy-nats))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Chunked серии
|
||||||
|
|
||||||
|
Мързеливите серии на Clojure са chunked (типично 32 елемента):
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Range създава chunked серии
|
||||||
|
(class (range 100)) ;; => clojure.lang.LongRange
|
||||||
|
|
||||||
|
;; Всеки chunk се реализира наведнъж
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Lazy Cons и реализация
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; cons създава мързелива серия
|
||||||
|
(def custom-seq (cons 1 (lazy-seq (cons 2 ()))))
|
||||||
|
|
||||||
|
;; lazy-seq отлага изчисленията
|
||||||
|
(defn fibs []
|
||||||
|
(cons 0
|
||||||
|
(cons 1
|
||||||
|
(map + (fibs) (rest (fibs))))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Seqable обекти
|
||||||
|
|
||||||
|
Всеки обект може да бъде направен последователен чрез имплементиране на `seq`:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(extend-type String
|
||||||
|
clojure.core.protocols/Coll
|
||||||
|
(coll [s] (seq s)))
|
||||||
|
|
||||||
|
;; Сега низовете работят със серийни функции
|
||||||
|
(map clojure.string/upper-case "hello")
|
||||||
|
;; => (\H \E \L \L \O)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 Безкрайни серии
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Повтарящ се цикъл
|
||||||
|
(def repeating (cycle [:a :b :c]))
|
||||||
|
|
||||||
|
;; Повтаряне завинаги
|
||||||
|
(def ones (repeatedly 1))
|
||||||
|
(def randoms (repeatedly #(rand-int 100)))
|
||||||
|
|
||||||
|
;; Iterate - прилага функция към предишния резултат
|
||||||
|
(def powers-of-two (iterate #(* 2 %) 1))
|
||||||
|
(def collatz (iterate #(if (even? %) (/ % 2) (inc (* 3 %))) 1))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.6 Производителност на сериите
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Не дръжте head на мързелива серия
|
||||||
|
(defn bad-sum []
|
||||||
|
(let [large-seq (range 10000000)]
|
||||||
|
(reduce + (take 10 large-seq)))) ;; Държи референция към цялата серия
|
||||||
|
|
||||||
|
(defn good-sum []
|
||||||
|
(reduce + (take 10 (range 10000000)))) ;; Head може да бъде GC'd
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.7 Eager vs Lazy
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; mapcat може да бъде eager
|
||||||
|
(mapcat reverse [[1 2] [3 4]]) ;; => (2 1 4 3)
|
||||||
|
|
||||||
|
;; into принуждава реализация
|
||||||
|
(into [] (map inc (range 1000)))
|
||||||
|
|
||||||
|
;; into е ефективен - не създава междинни колекции
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Трансдюсъри
|
||||||
|
|
||||||
|
Трансдюсърите са съставни, мързеливи трансформации, независими от входния контекст.
|
||||||
|
|
||||||
|
### 3.1 Създаване на трансдюсъри
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Без контекст
|
||||||
|
(def increment (map inc))
|
||||||
|
(def only-evens (filter even?))
|
||||||
|
|
||||||
|
;; Съставяне на трансдюсъри
|
||||||
|
(def transform (comp
|
||||||
|
(filter even?)
|
||||||
|
(map inc)
|
||||||
|
(take 10)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Използване на трансдюсъри
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; С всякаква последователна колекция
|
||||||
|
(transduce transform + (range 100))
|
||||||
|
;; => Сума на първите 10 четни числа + 1
|
||||||
|
|
||||||
|
(into [] transform (range 100))
|
||||||
|
;; => [3 5 7 9 11 13 15 17 19 21]
|
||||||
|
|
||||||
|
(sequence transform (range 100))
|
||||||
|
;; => Връща мързелива серия
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Завършващи редукции
|
||||||
|
|
||||||
|
Някои трансдюсъри трябва да направят нещо в края:
|
||||||
|
|
||||||
|
```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 Ранно прекратяване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; reduced увива стойност за спиране рано
|
||||||
|
(transduce (filter odd?) + (range 10))
|
||||||
|
;; => 25 (1+3+5+7+9)
|
||||||
|
|
||||||
|
;; Използвайте reduced? за проверка
|
||||||
|
(reduced? (reduced 5)) ;; => true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 Cat и завършване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[clojure.core.protocols :as p])
|
||||||
|
|
||||||
|
;; Завършващата арност на редуциращата функция
|
||||||
|
(transduce
|
||||||
|
(map inc)
|
||||||
|
(fn
|
||||||
|
([result] result) ;; завършваща арност
|
||||||
|
([result input] (rf result input)))
|
||||||
|
[]
|
||||||
|
(range 5))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Specs и валидация
|
||||||
|
|
||||||
|
### 4.1 Въведение в Spec
|
||||||
|
|
||||||
|
Spec предоставя валидация по време на изпълнение и генеративно тестване (чрез `clojure.spec.gen`).
|
||||||
|
|
||||||
|
### 4.2 Дефиниране на 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 Конформиране
|
||||||
|
|
||||||
|
```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 Валидация с `valid?`
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(s/valid? ::age 25) ;; => true
|
||||||
|
(s/valid? ::age -5) ;; => false
|
||||||
|
(s/valid? ::person {::name "John" ::age 30}) ;; => true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.5 Генеративно тестване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[clojure.spec.gen.alpha :as gen])
|
||||||
|
|
||||||
|
;; Генериране на стойности
|
||||||
|
(gen/generate (s/gen ::age))
|
||||||
|
(gen/sample (s/gen ::age))
|
||||||
|
|
||||||
|
;; Тестване със spec
|
||||||
|
(s/def ::email (s/and string?
|
||||||
|
#(re-find #"@" %)))
|
||||||
|
|
||||||
|
(s/fdef greet
|
||||||
|
:args (s/cat :name ::name)
|
||||||
|
:ret string?)
|
||||||
|
|
||||||
|
;; Пускане на генеративни тестове
|
||||||
|
(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. Протоколът Collection
|
||||||
|
|
||||||
|
### 5.1 Йерархия на колекциите
|
||||||
|
|
||||||
|
```
|
||||||
|
IPersistentCollection
|
||||||
|
IPersistentList
|
||||||
|
IPersistentVector
|
||||||
|
IPersistentMap
|
||||||
|
IPersistentSet
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Ключови протоколи
|
||||||
|
|
||||||
|
```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 Разширяване на колекции
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Използване на 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 Персонализирани Reducibles
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defrecord Range [start end]
|
||||||
|
clojure.core.protocols/Coll
|
||||||
|
(coll [this] (seq (range start end)))
|
||||||
|
|
||||||
|
(reduce + (Range. 1 10)) ;; => 45
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Reducibles
|
||||||
|
|
||||||
|
Reduciers предоставят начин за извършване на паралелни редукции без мързеливи серии.
|
||||||
|
|
||||||
|
### 6.1 Използване на Reducers
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[clojure.core.reducers :as r])
|
||||||
|
|
||||||
|
;; Паралелна map (автоматично паралелизира в fold)
|
||||||
|
(r/map inc (range 1000))
|
||||||
|
|
||||||
|
;; fold използва паралелна редукция
|
||||||
|
(r/fold + (r/map inc (range 1000000)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Персонализирани Reducers
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; fold изисква foldable колекция и комбинираща функция
|
||||||
|
(r/fold
|
||||||
|
(fn ([] 0) ([x y] (+ x y)))
|
||||||
|
(fn ([x] x) ([x y] (+ x y)))
|
||||||
|
(range 1000))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Паралелизъм
|
||||||
|
|
||||||
|
### 7.1 pmap
|
||||||
|
|
||||||
|
Паралелна map (мързелива):
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Като map, но се изпълнява паралелно
|
||||||
|
(time
|
||||||
|
(doall (pmap #(do (Thread/sleep 100) %) (range 10))))
|
||||||
|
;; Много по-бързо от обикновена map със задръстващи операции
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Reducers за паралелизъм
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Сгъване с множество ядра
|
||||||
|
(r/fold 100 + (range 10000000))
|
||||||
|
|
||||||
|
;; Персонализирана комбинираща функция
|
||||||
|
(r/fold
|
||||||
|
100
|
||||||
|
(fn ([] 0) ([a b] (+ a b)))
|
||||||
|
(fn ([] 0) ([a b] (+ a b)))
|
||||||
|
(range 10000000))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Futures
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Независими паралелни задачи
|
||||||
|
(let [a (future (compute-a))
|
||||||
|
b (future (compute-b))]
|
||||||
|
[@a @b]) ;; Изчаква и двете
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 CompletableFuture (само бележка)
|
||||||
|
|
||||||
|
Забележка: Java's `CompletableFuture` изисква Java interop. Чисти алтернативи на Clojure включват:
|
||||||
|
- Core.async канали
|
||||||
|
- Manifold библиотека
|
||||||
|
- Promises с futures
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Оптимизация на производителността
|
||||||
|
|
||||||
|
### 8.1 Persistent структури от данни
|
||||||
|
|
||||||
|
Persistent структурите от данни на Clojure споделят структура:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Добавяне към вектор споделя повечето структура
|
||||||
|
(def v1 [1 2 3 4 5])
|
||||||
|
(def v2 (conj v1 6))
|
||||||
|
|
||||||
|
;; v1 и v2 споделят [1 2 3 4 5]
|
||||||
|
;; Само нови възли се създават за пътя към новия елемент
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Transient структури от данни
|
||||||
|
|
||||||
|
За локални, временни мутации:
|
||||||
|
|
||||||
|
```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))) ;; По-бавно
|
||||||
|
(time (count (fast-accumulation))) ;; По-бързо
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 Chunked операции
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Предпочитайте chunked операции
|
||||||
|
(into [] (map inc (range 1000))) ;; Създава една междинна серия
|
||||||
|
(into [] (mapcat list (range 100))) ;; Изравнява мързеливо
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 Поддържане на аргументи eager
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Лошо: дръж head на серията
|
||||||
|
(def bad-result (map f large-collection))
|
||||||
|
|
||||||
|
;; Добро: обработвайте незабавно
|
||||||
|
(into [] (map f large-collection))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.5 Batch обработка
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Вместо много малки операции
|
||||||
|
(doseq [x items]
|
||||||
|
(update-db x))
|
||||||
|
|
||||||
|
;; Помислете за batch-ване
|
||||||
|
(batch-update items)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.6 Предварително зареждане и кеширане
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Мемоизация за скъпи изчисления
|
||||||
|
(def cached-expensive-lookup
|
||||||
|
(memoize (fn [k]
|
||||||
|
(compute-expensively k))))
|
||||||
|
|
||||||
|
;; Предварително зареждане при стартиране
|
||||||
|
(def initialized-data
|
||||||
|
(delay (load-and-process-data)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.7 Бенчмаркинг
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[criterium.core :as c])
|
||||||
|
|
||||||
|
(c/quick-bench (reduce + (range 10000)))
|
||||||
|
;; Докладва mean, std deviation и т.н.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Индекс
|
||||||
|
|
||||||
|
### A
|
||||||
|
|
||||||
|
- `arity` - [1.8](#18-арности-и-претоварване)
|
||||||
|
- `assert` - [1.6](#16-пред--и-пост-условия)
|
||||||
|
|
||||||
|
### C
|
||||||
|
|
||||||
|
- `chunked-seq?` - [2.2](#22-chunked-серии)
|
||||||
|
- `coll` - [5.3](#53-разширяване-на-колекции)
|
||||||
|
- `complement` - [1.3](#13-аргументи-от-тип-ключова-дума)
|
||||||
|
- `comp` - [1.3](#13-аргументи-от-тип-ключова-дума)
|
||||||
|
|
||||||
|
### D
|
||||||
|
|
||||||
|
- `delay` - [2.6](#26-производителност-на-сериите)
|
||||||
|
- `delayed?` - [2.6](#26-производителност-на-сериите)
|
||||||
|
- `deref` - [2.6](#26-производителност-на-сериите)
|
||||||
|
|
||||||
|
### F
|
||||||
|
|
||||||
|
- `force` - [2.6](#26-производителност-на-сериите)
|
||||||
|
- `fnil` - [1.3](#13-аргументи-от-тип-ключова-дума)
|
||||||
|
- `fold` - [6.2](#62-използване-на-reducers)
|
||||||
|
- `fpartial` - [1.3](#13-аргументи-от-тип-ключова-дума)
|
||||||
|
|
||||||
|
### G
|
||||||
|
|
||||||
|
- `gen` - [4.5](#45-генеративно-тестване)
|
||||||
|
- `generate` - [4.5](#45-генеративно-тестване)
|
||||||
|
|
||||||
|
### I
|
||||||
|
|
||||||
|
- `into` - [3.2](#32-използване-на-трансдюсъри)
|
||||||
|
- `iterate` - [2.5](#25-безкрайни-серии)
|
||||||
|
|
||||||
|
### L
|
||||||
|
|
||||||
|
- `lazy-cat` - [2.3](#23-lazy-cons-и-реализация)
|
||||||
|
- `lazy-seq` - [2.3](#23-lazy-cons-и-реализация)
|
||||||
|
- `let` - [1.2](#12-rest-параметри-в-детайли)
|
||||||
|
|
||||||
|
### M
|
||||||
|
|
||||||
|
- `memoize` - [1.5](#15-мемоизация)
|
||||||
|
- `multi-spec` - [4.6](#46-multi-spec)
|
||||||
|
- `mmerge` - [6.1](#61-използване-на-reducers)
|
||||||
|
|
||||||
|
### N
|
||||||
|
|
||||||
|
- `nested` - [5.3](#53-разширяване-на-колекции)
|
||||||
|
- `next` - [5.2](#52-ключови-протоколи)
|
||||||
|
|
||||||
|
### P
|
||||||
|
|
||||||
|
- `parallelize` - [7.2](#72-reducers-за-паралелизъм)
|
||||||
|
- `partial` - [1.3](#13-аргументи-от-тип-ключова-дума)
|
||||||
|
- `pmap` - [7.1](#71-pmap)
|
||||||
|
- `promote` - [6.2](#62-използване-на-reducers)
|
||||||
|
|
||||||
|
### R
|
||||||
|
|
||||||
|
- `realized?` - [2.1](#21-реализиране-на-серии)
|
||||||
|
- `reduced` - [3.4](#34-ранно-прекратяване)
|
||||||
|
- `reduced?` - [3.4](#34-ранно-прекратяване)
|
||||||
|
- `reductions` - [3.3](#33-завършващи-редукции)
|
||||||
|
|
||||||
|
### S
|
||||||
|
|
||||||
|
- `sample` - [4.5](#45-генеративно-тестване)
|
||||||
|
- `sequence` - [3.2](#32-използване-на-трансдюсъри)
|
||||||
|
- `spec` - [4.1](#41-въведение-в-spec)
|
||||||
|
- `split-with` - [2.6](#26-производителност-на-сериите)
|
||||||
|
|
||||||
|
### T
|
||||||
|
|
||||||
|
- `test` - [4.5](#45-генеративно-тестване)
|
||||||
|
- `transduce` - [3.2](#32-използване-на-трансдюсъри)
|
||||||
|
- `transient` - [8.2](#82-transient-структури-от-данни)
|
||||||
|
- `tree-seq` - [2.6](#26-производителност-на-сериите)
|
||||||
|
|
||||||
|
### V
|
||||||
|
|
||||||
|
- `volatile!` - [1.7](#17-метаданни-на-функции)
|
||||||
|
- `volatile?` - [1.7](#17-метаданни-на-функции)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Чист Clojure: Разширени теми*
|
||||||
@@ -0,0 +1,629 @@
|
|||||||
|
# Чист Clojure: Структура на проекта и инструменти
|
||||||
|
|
||||||
|
## Съдържание
|
||||||
|
|
||||||
|
1. [Структура на проекта](#1-структура-на-проекта)
|
||||||
|
2. [deps.edn и CLI инструменти](#2-depsedn-и-cli-инструменти)
|
||||||
|
3. [Инфраструктура за тестване](#3-инфраструктура-за-тестване)
|
||||||
|
4. [Работен процес за разработка](#4-работен-процес-за-разработка)
|
||||||
|
5. [Инструменти за качество на кода](#5-инструменти-за-качество-на-кода)
|
||||||
|
6. [Изграждане и деплойване](#6-изграждане-и-деплойване)
|
||||||
|
7. [Екосистема от библиотеки](#7-екосистема-от-библиотеки)
|
||||||
|
8. [Техники за дебъгване](#8-техники-за-дебъгване)
|
||||||
|
9. [Индекс](#9-индекс)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Структура на проекта
|
||||||
|
|
||||||
|
### 1.1 Типична структура на проект
|
||||||
|
|
||||||
|
```
|
||||||
|
myproject/
|
||||||
|
├── deps.edn
|
||||||
|
├── src/
|
||||||
|
│ └── myproject/
|
||||||
|
│ ├── core.clj
|
||||||
|
│ ├── util.clj
|
||||||
|
│ └── spec/
|
||||||
|
│ └── core_spec.clj
|
||||||
|
├── test/
|
||||||
|
│ └── myproject/
|
||||||
|
│ ├── core_test.clj
|
||||||
|
│ └── util_test.clj
|
||||||
|
├── resources/
|
||||||
|
├── doc/
|
||||||
|
│ └── intro.md
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Namespace като файлови пътища
|
||||||
|
|
||||||
|
Namespaces се мапват към файлови пътища:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; src/myproject/core.clj
|
||||||
|
(ns myproject.core)
|
||||||
|
|
||||||
|
;; src/myproject/util.clj
|
||||||
|
(ns myproject.util)
|
||||||
|
|
||||||
|
;; src/myproject/spec/user.clj
|
||||||
|
(ns myproject.spec.user)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Multi-module проекти
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; deps.edn с множество модули
|
||||||
|
{:paths ["src" "modules/common/src"]
|
||||||
|
:deps {org.clojure/clojure {:mvn/version "1.11.1"}}
|
||||||
|
:aliases
|
||||||
|
{:dev {:extra-paths ["test"]
|
||||||
|
:extra-deps {}}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 Разделяне на Source и Test
|
||||||
|
|
||||||
|
- Source код: директория `src/`
|
||||||
|
- Тестове: директория `test/`
|
||||||
|
- И двете се добавят към classpath по време на разработка
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. deps.edn и CLI инструменти
|
||||||
|
|
||||||
|
### 2.1 Референция на deps.edn
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}
|
||||||
|
org.clojure/data.json {:mvn/version "2.4.0"}
|
||||||
|
clojure.java-time/clojure.java-time {:mvn/version "1.4.2"}
|
||||||
|
}
|
||||||
|
:paths {:src ["src"]
|
||||||
|
:test ["test"]}
|
||||||
|
:aliases
|
||||||
|
{:test {:extra-paths ["test"]
|
||||||
|
:extra-deps {org.clojure/test.check {:mvn/version "1.1.1"}}}
|
||||||
|
:dev {:jvm-opts ["-Xmx4g"]}
|
||||||
|
:bench {:extra-deps {criterium/criterium {:mvn/version "0.4.4"}}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Пускане на код
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Пускане на скрипт
|
||||||
|
clj -M script.clj
|
||||||
|
|
||||||
|
# Пускане с test alias
|
||||||
|
clj -M:test -m myproject.test-runner
|
||||||
|
|
||||||
|
# Пускане на REPL с deps
|
||||||
|
clj -M
|
||||||
|
|
||||||
|
# Пускане с конкретни deps
|
||||||
|
clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.11.1"}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Разбиране на aliases
|
||||||
|
|
||||||
|
Aliases модифицират classpath или поведение:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Тест с generative проверка
|
||||||
|
clj -M:test:gen
|
||||||
|
|
||||||
|
# Production build
|
||||||
|
clj -M:prod build
|
||||||
|
|
||||||
|
# Dev mode с допълнителни проверки
|
||||||
|
clj -M:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Създаване на зависимости
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; deps.edn
|
||||||
|
{:deps {io.github.clojure/data.json {:git/sha "..."}}}
|
||||||
|
|
||||||
|
;; Classpath
|
||||||
|
clj -Sdescribe # Покажи ефективния classpath
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Инфраструктура за тестване
|
||||||
|
|
||||||
|
### 3.1 test.check за генеративно тестване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[clojure.test.check :as tc]
|
||||||
|
'[clojure.test.check.generators :as gen]
|
||||||
|
'[clojure.test.check.properties :as prop])
|
||||||
|
|
||||||
|
(def prop-sort-idempotent
|
||||||
|
(prop/for-all
|
||||||
|
[v (gen/vector gen/int)]
|
||||||
|
(= (sort v) (sort (sort v)))))
|
||||||
|
|
||||||
|
(tc/quick-check 100 prop-sort-idempotent)
|
||||||
|
;; => {:result true, :pass? true, ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Fixtures за setup/teardown
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myproject.test-util
|
||||||
|
(:require [clojure.test :as t]
|
||||||
|
[myproject.db :as db]))
|
||||||
|
|
||||||
|
(defn with-test-db [f]
|
||||||
|
(db/connect! :test)
|
||||||
|
(f)
|
||||||
|
(db/disconnect!))
|
||||||
|
|
||||||
|
(defn with-logging [f]
|
||||||
|
(println "Преди тест")
|
||||||
|
(f)
|
||||||
|
(println "След тест"))
|
||||||
|
|
||||||
|
;; Прилагане на fixtures
|
||||||
|
(t/use-fixtures :each with-test-db with-logging)
|
||||||
|
(t/use-fixtures :once db/setup-once)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Тестови namespace-и
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myproject.core-test
|
||||||
|
(:require [clojure.test :as t]
|
||||||
|
[myproject.core :as core]))
|
||||||
|
|
||||||
|
(t/deftest ^:integration api-test
|
||||||
|
"Интеграционни тестове за външно API"
|
||||||
|
...)
|
||||||
|
|
||||||
|
;; Пускане на конкретни тестове
|
||||||
|
(t/run-tests 'myproject.core-test)
|
||||||
|
(t/run-tests #"myproject.*test")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Expectations-style тестване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Използване на expectations библиотека
|
||||||
|
(require '[expectations :refer [expect]])
|
||||||
|
|
||||||
|
(expect 4 (+ 2 2))
|
||||||
|
(expect ArithmeticException (/ 1 0))
|
||||||
|
(expect [:a :b :c] (filterv odd? [1 2 3 4]))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 Midje (за четимост)
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[midje.sweet :refer [fact facts =>]])
|
||||||
|
|
||||||
|
(fact "събирането работи"
|
||||||
|
(+ 2 2) => 4)
|
||||||
|
|
||||||
|
(facts "за низовете"
|
||||||
|
(fact "upper-case работи"
|
||||||
|
(clojure.string/upper-case "hello") => "HELLO"))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Работен процес за разработка
|
||||||
|
|
||||||
|
### 4.1 REPL-driven разработка
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Стартиране на REPL
|
||||||
|
user=> (require '[myproject.core :as core] :reload)
|
||||||
|
|
||||||
|
;; Редактиране на код, презареждане
|
||||||
|
user=> (require '[myproject.core :as core] :reload :verbose)
|
||||||
|
|
||||||
|
;; Презареждане на всички променени namespaces
|
||||||
|
user=> (require '[clojure.tools.namespace.repl :as ns]
|
||||||
|
:refer [refresh refresh-all])
|
||||||
|
user=> (refresh)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Hot Loading на код
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; В REPL
|
||||||
|
(def server (atom nil))
|
||||||
|
|
||||||
|
(defn start-server []
|
||||||
|
(swap! server assoc :running true))
|
||||||
|
|
||||||
|
;; След редактиране на core, просто презаредете
|
||||||
|
(require '[myproject.core :as core] :reload)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Проследяване на source
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Проследяване на променени файлове
|
||||||
|
(require '[clojure.java.io :as io]
|
||||||
|
'[clojure.tools.namespace.track :as track])
|
||||||
|
|
||||||
|
(defn track-changes []
|
||||||
|
(let [tracker (atom (track/tracker))]
|
||||||
|
(fn []
|
||||||
|
(swap! tracker track/step)
|
||||||
|
(track/deps @tracker))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Богати comment блокове
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn process-data [input]
|
||||||
|
{:pre [(sequential? input)]}
|
||||||
|
(mapv inc input))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Експерименти по време на разработка
|
||||||
|
(process-data [1 2 3])
|
||||||
|
|
||||||
|
;; Тестване на гранични случаи
|
||||||
|
(process-data [])
|
||||||
|
|
||||||
|
;; Интерактивно дебъгване
|
||||||
|
(def test-data (load-data))
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Инструменти за качество на кода
|
||||||
|
|
||||||
|
### 5.1 eastwood (Linter)
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; deps.edn alias
|
||||||
|
:lint {:extra-deps {lancem待/clojure-eastwood {:mvn/version "..."}}}
|
||||||
|
|
||||||
|
;; Пускане
|
||||||
|
clj -M:lint -m eastwood.lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 clj-kondo (Бърз Linter)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Инсталиране
|
||||||
|
brew install clj-kondo
|
||||||
|
|
||||||
|
# Пускане върху проект
|
||||||
|
clj-kondo --lint src/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Форматиране с cljfmt
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; deps.edn
|
||||||
|
:format {:extra-deps {com.github.cljfmt/cljfmt {:mvn/version "..."}}}
|
||||||
|
|
||||||
|
# Проверка
|
||||||
|
clj -M:format check src/
|
||||||
|
|
||||||
|
# Поправка
|
||||||
|
clj -M:format fix src/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Typed Clojure (Незадължително проверяване на типове)
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[clojure.core.typed :as t])
|
||||||
|
|
||||||
|
(t/ann my-function [t/Int -> t/Int])
|
||||||
|
(defn my-function [x]
|
||||||
|
(inc x))
|
||||||
|
|
||||||
|
;; Проверка на типовете
|
||||||
|
(t/check-ns)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Изграждане и деплойване
|
||||||
|
|
||||||
|
### 6.1 Изграждане на Uberjars
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; tools.build
|
||||||
|
(require '[clojure.tools.build.api :as b])
|
||||||
|
|
||||||
|
(def lib 'com.mycompany/myapp)
|
||||||
|
(def version "1.0.0")
|
||||||
|
(def target-dir "target")
|
||||||
|
|
||||||
|
(defn uberjar [opts]
|
||||||
|
(b/java-command
|
||||||
|
{:basis (:basis opts)
|
||||||
|
:main 'myproject.core
|
||||||
|
:jar-file (b/path target-dir "myapp.jar")
|
||||||
|
:uber-jar true}))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Native Image с GraalVM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Компилиране на Clojure до JVM bytecode първо
|
||||||
|
clojure -M:build compile
|
||||||
|
|
||||||
|
# Изграждане на native image
|
||||||
|
native-image --initialize-at-build-time=clojure.lang.RT \
|
||||||
|
-jar myapp.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Docker интеграция
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM clojure:openjdk-17-lein
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY deps.edn .
|
||||||
|
RUN clj -M -P
|
||||||
|
|
||||||
|
COPY src src
|
||||||
|
COPY test test
|
||||||
|
|
||||||
|
ENVlein uberjar
|
||||||
|
ENTRYPOINT ["java", "-jar", "target/myapp.jar"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 CI/CD Pipeline
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# GitHub Actions пример
|
||||||
|
name: Clojure CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: DeLaGuardo/setup-clojure@10
|
||||||
|
with:
|
||||||
|
backend: 'cli'
|
||||||
|
version: '1.11.1'
|
||||||
|
- run: clj -M:test
|
||||||
|
- run: clj -M:lint
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Екосистема от библиотеки
|
||||||
|
|
||||||
|
### 7.1 Web разработка
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; HTTP Сървър - Ring (чист Clojure)
|
||||||
|
{:deps {ring/ring {:mvn/version "1.9.5"}}
|
||||||
|
|
||||||
|
;; Routes - Compojure
|
||||||
|
{:deps {compojure/compojure {:mvn/version "1.6.2"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Обработка на данни
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Манипулация на данни
|
||||||
|
{:deps {zipkin/zikaron {:mvn/version "1.0.0"}}}
|
||||||
|
|
||||||
|
;; CSV handling
|
||||||
|
{:deps {org.clojure/data.csv {:mvn/version "1.0.1"}}
|
||||||
|
|
||||||
|
;; JSON (чист Clojure)
|
||||||
|
{:deps {org.clojure/data.json {:mvn/version "2.4.0"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Асинхронно програмиране
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Core async е вграден
|
||||||
|
;; Няма допълнителни зависимости за канали
|
||||||
|
|
||||||
|
;; За допълнителни async модели
|
||||||
|
{:deps {manifold/manifold {:mvn/version "0.4.0"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 Достъп до бази данни
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Чист Clojure JDBC wrapper
|
||||||
|
{:deps {org.clojure/java.jdbc {:mvn/version "0.7.12"}}
|
||||||
|
|
||||||
|
;; SQL DSL
|
||||||
|
{:deps {sqlkorma/sqlkorma {:mvn/version "0.4.0"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.5 Библиотеки за тестване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
{:deps {expectations/expectations {:mvn/version "2.0.0"}}
|
||||||
|
{midje/midje {:mvn/version "1.9.10"}}
|
||||||
|
{org.clojure/test.check {:mvn/version "1.1.1"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.6 Намиране на библиотеки
|
||||||
|
|
||||||
|
- Clojars.org - Общностно хранилище
|
||||||
|
- Clojuredocs.org - Документация с примери
|
||||||
|
- Awesome-clojure - Куриран списък
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Техники за дебъгване
|
||||||
|
|
||||||
|
### 8.1 Print дебъгване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Прости отпечатвания
|
||||||
|
(println "Debug:" variable)
|
||||||
|
|
||||||
|
;; С pretty printer
|
||||||
|
(require '[clojure.pprint :as pp])
|
||||||
|
(pp/pprint data-structure)
|
||||||
|
|
||||||
|
;; Tap за дебъгване
|
||||||
|
(tap> {:event :processing :data x})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Stack traces
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Вземете пълен stack trace
|
||||||
|
(.printStackTrace *e)
|
||||||
|
|
||||||
|
;; Ex-data за spec грешки
|
||||||
|
(try
|
||||||
|
(s/conform ::spec value)
|
||||||
|
(catch Exception e
|
||||||
|
(ex-data e)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 REPL дебъгване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Инспектиране на стойности
|
||||||
|
user> (def data (s/gen ::my-spec))
|
||||||
|
user> data
|
||||||
|
|
||||||
|
;; Стъпка по стъпка с trace
|
||||||
|
(trace (reduce + (range 10)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 Watching state
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Watch atoms
|
||||||
|
(add-watch my-atom :debug
|
||||||
|
(fn [k r o n]
|
||||||
|
(println "Променено:" o "->" n)))
|
||||||
|
|
||||||
|
;; Watch refs в транзакция
|
||||||
|
(dosync
|
||||||
|
(trace (alter my-ref f)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.5 Breakpoints
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Използване на dbg macro (от tools.trace)
|
||||||
|
(require '[clojure.tools.trace :as t])
|
||||||
|
(t/dbg expression)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.6 Logging
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[clojure.tools.logging :as log])
|
||||||
|
|
||||||
|
(log/info "Приложението стартира")
|
||||||
|
(log/debug "Обработка" :item item)
|
||||||
|
(log/error e "Неуспех при обработка")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Индекс
|
||||||
|
|
||||||
|
### A
|
||||||
|
|
||||||
|
- `add-watch` - [8.4](#84-watching-state)
|
||||||
|
- `alias` - [1.2](#12-референция-на-depsedn)
|
||||||
|
|
||||||
|
### B
|
||||||
|
|
||||||
|
- `build` - [6.1](#61-изграждане-на-uberjars)
|
||||||
|
|
||||||
|
### C
|
||||||
|
|
||||||
|
- `check-ns` - [5.4](#54-typed-clojure-незадължително-проверяване-на-типове)
|
||||||
|
- `clojure.tools.namespace` - [4.1](#41-repl-driven-разработка)
|
||||||
|
- `comment` - [4.4](#44-богати-comment-блокове)
|
||||||
|
|
||||||
|
### D
|
||||||
|
|
||||||
|
- `dbg` - [8.5](#85-breakpoints)
|
||||||
|
- `deps.edn` - [2.1](#21-референция-на-depsedn)
|
||||||
|
|
||||||
|
### E
|
||||||
|
|
||||||
|
- `eastwood` - [5.1](#51-eastwood-linter)
|
||||||
|
|
||||||
|
### F
|
||||||
|
|
||||||
|
- `find-libs` - [7.6](#76-намиране-на-библиотеки)
|
||||||
|
|
||||||
|
### G
|
||||||
|
|
||||||
|
- `gen` - [3.1](#31-testcheck-за-генеративно-тестване)
|
||||||
|
|
||||||
|
### I
|
||||||
|
|
||||||
|
- `instrument` - [3.1](#31-testcheck-за-генеративно-тестване)
|
||||||
|
|
||||||
|
### L
|
||||||
|
|
||||||
|
- `lein` - [6.3](#63-docker-интеграция)
|
||||||
|
- `load-file` - [4.2](#42-hot-loading-на-код)
|
||||||
|
|
||||||
|
### M
|
||||||
|
|
||||||
|
- `memoize` - [4.3](#43-проследяване-на-source)
|
||||||
|
- `merge` - [8.2](#82-stack-traces)
|
||||||
|
|
||||||
|
### N
|
||||||
|
|
||||||
|
- `ns-publics` - [4.1](#41-repl-driven-разработка)
|
||||||
|
|
||||||
|
### P
|
||||||
|
|
||||||
|
- `pp/pprint` - [8.1](#81-print-дебъгване)
|
||||||
|
- `profile` - [8.3](#83-repl-дебъгване)
|
||||||
|
|
||||||
|
### Q
|
||||||
|
|
||||||
|
- `quick-check` - [3.1](#31-testcheck-за-генеративно-тестване)
|
||||||
|
|
||||||
|
### R
|
||||||
|
|
||||||
|
- `reduce` - [8.3](#83-repl-дебъгване)
|
||||||
|
- `refresh` - [4.1](#41-repl-driven-разработка)
|
||||||
|
- `reload` - [4.1](#41-repl-driven-разработка)
|
||||||
|
- `run-tests` - [3.3](#33-тестови-namespace-и)
|
||||||
|
|
||||||
|
### S
|
||||||
|
|
||||||
|
- `s/conform` - [8.2](#82-stack-traces)
|
||||||
|
- `s/gen` - [3.1](#31-testcheck-за-генеративно-тестване)
|
||||||
|
- `shadow` - [6.2](#62-native-image-с-graalvm)
|
||||||
|
- `spec` - [8.2](#82-stack-traces)
|
||||||
|
|
||||||
|
### T
|
||||||
|
|
||||||
|
- `tap>` - [8.1](#81-print-дебъгване)
|
||||||
|
- `test` - [3.3](#33-тестови-namespace-и)
|
||||||
|
- `trace` - [8.5](#85-breakpoints)
|
||||||
|
- `track` - [4.3](#43-проследяване-на-source)
|
||||||
|
|
||||||
|
### U
|
||||||
|
|
||||||
|
- `uberjar` - [6.1](#61-изграждане-на-uberjars)
|
||||||
|
- `use-fixtures` - [3.2](#32-fixtures-за-setupteardown)
|
||||||
|
|
||||||
|
### V
|
||||||
|
|
||||||
|
- `verify` - [3.1](#31-testcheck-за-генеративно-тестване)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Чист Clojure: Структура на проекта и инструменти*
|
||||||
@@ -0,0 +1,693 @@
|
|||||||
|
# Чист Clojure: Практически рецепти
|
||||||
|
|
||||||
|
## Съдържание
|
||||||
|
|
||||||
|
1. [Често срещани модели](#1-често-срещани-модели)
|
||||||
|
2. [Рецепти за трансформация на данни](#2-рецепти-за-трансформация-на-данни)
|
||||||
|
3. [Рецепти за управление на състояние](#3-рецепти-за-управление-на-състояние)
|
||||||
|
4. [Async рецепти](#4-async-рецепти)
|
||||||
|
5. [Рецепти за валидация](#5-рецепти-за-валидация)
|
||||||
|
6. [Модели за дизайн на API](#6-модели-за-дизайн-на-api)
|
||||||
|
7. [Рецепти за тестване](#7-рецепти-за-тестване)
|
||||||
|
8. [Рецепти за производителност](#8-рецепти-за-производителност)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Често срещани модели
|
||||||
|
|
||||||
|
### 1.1 Maybe/Option модел
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn safe-divide [a b]
|
||||||
|
(if (zero? b)
|
||||||
|
nil
|
||||||
|
(/ a b)))
|
||||||
|
|
||||||
|
(defn map-safe [f coll]
|
||||||
|
(sequence (comp (filter some?)
|
||||||
|
(map f))
|
||||||
|
coll))
|
||||||
|
|
||||||
|
(map-safe #(safe-divide 10 %) [2 0 4 0 5])
|
||||||
|
;; => (5 2 2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Either/Result модел
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn parse-int [s]
|
||||||
|
(try
|
||||||
|
{:success (Long/parseLong s)}
|
||||||
|
(catch NumberFormatException _
|
||||||
|
{:error "Невалидно число"})))
|
||||||
|
|
||||||
|
(defn bind [result f]
|
||||||
|
(if (:error result)
|
||||||
|
result
|
||||||
|
(f (:success result))))
|
||||||
|
|
||||||
|
(-> (parse-int "42")
|
||||||
|
(bind #(* % 2))
|
||||||
|
(bind #(+ % 1)))
|
||||||
|
;; => {:success 85}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Модел State Machine
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defprotocol StateMachine
|
||||||
|
(transition [state event])
|
||||||
|
(current-state [state]))
|
||||||
|
|
||||||
|
(defrecord TrafficLight [state]
|
||||||
|
StateMachine
|
||||||
|
(transition [_ event]
|
||||||
|
(case [state event]
|
||||||
|
[:green :timeout] (->TrafficLight :yellow)
|
||||||
|
[:yellow :timeout] (->TrafficLight :red)
|
||||||
|
[:red :timeout] (->TrafficLight :green)
|
||||||
|
_))
|
||||||
|
(current-state [_] state))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 Builder модел
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn make-builder [defaults]
|
||||||
|
(let [state (atom defaults)]
|
||||||
|
(reify
|
||||||
|
Object
|
||||||
|
(toString [_] (str @state))
|
||||||
|
clojure.core.protocols/Coll
|
||||||
|
(coll [_] (seq @state))
|
||||||
|
clojure.lang.IFn
|
||||||
|
(invoke [_ k v]
|
||||||
|
(swap! state assoc k v)
|
||||||
|
this)
|
||||||
|
(invoke [_ m]
|
||||||
|
(swap! state merge m)
|
||||||
|
this))))
|
||||||
|
|
||||||
|
(def builder (make-builder {:debug false :timeout 5000}))
|
||||||
|
(-> builder
|
||||||
|
(assoc :host "localhost")
|
||||||
|
(merge {:port 8080})
|
||||||
|
str)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Рецепти за трансформация на данни
|
||||||
|
|
||||||
|
### 2.1 Вложен достъп до данни
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn get-in-safe [m keys default]
|
||||||
|
(try
|
||||||
|
(get-in m keys)
|
||||||
|
(catch NullPointerException _
|
||||||
|
default)))
|
||||||
|
|
||||||
|
;; Със spec валидация
|
||||||
|
(get-in-safe {:user {:address {:city "Sofia"}}}
|
||||||
|
[:user :address :city]
|
||||||
|
"Неизвестно")
|
||||||
|
|
||||||
|
;; Дълбоко обновление
|
||||||
|
(defn update-in-safe [m keys f & args]
|
||||||
|
(if (get-in m keys)
|
||||||
|
(apply update-in m keys f args)
|
||||||
|
m))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Групиране и агрегиране
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Групиране по множество ключове
|
||||||
|
(defn group-by-multiple [ks coll]
|
||||||
|
(reduce
|
||||||
|
(fn [acc item]
|
||||||
|
(update-in acc (map item ks) conj item))
|
||||||
|
{}
|
||||||
|
coll))
|
||||||
|
|
||||||
|
(group-by-multiple [:department :role]
|
||||||
|
[{:name "Alice" :department "Eng" :role "Dev"}
|
||||||
|
{:name "Bob" :department "Eng" :role "Dev"}
|
||||||
|
{:name "Carol" :department "Sales" :role "Mgr"}])
|
||||||
|
|
||||||
|
;; Rolling агрегации
|
||||||
|
(defn rolling [f n coll]
|
||||||
|
(let [window (vec (take n coll))]
|
||||||
|
(lazy-seq
|
||||||
|
(cons (f window)
|
||||||
|
(rolling f n (rest coll))))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Pivot Tables
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn pivot-table [data row-key col-key value-fn]
|
||||||
|
(reduce
|
||||||
|
(fn [table row]
|
||||||
|
(let [r (row-key row)
|
||||||
|
c (col-key row)
|
||||||
|
v (value-fn row)]
|
||||||
|
(assoc-in table [r c] v)))
|
||||||
|
{}
|
||||||
|
data))
|
||||||
|
|
||||||
|
(pivot-table [{:month "Jan" :region "East" :sales 100}
|
||||||
|
{:month "Jan" :region "West" :sales 150}
|
||||||
|
{:month "Feb" :region "East" :sales 200}]
|
||||||
|
:month :region :sales)
|
||||||
|
;; => {"Jan" {"East" 100 "West" 150}
|
||||||
|
"Feb" {"East" 200}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Операции с дървета
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Сума на всички числови листа
|
||||||
|
(defn tree-sum [tree]
|
||||||
|
(reduce + 0
|
||||||
|
(tree-seq sequential? seq tree)))
|
||||||
|
|
||||||
|
;; Map върху дърво
|
||||||
|
(defn tree-map [f tree]
|
||||||
|
(postwalk #(if (sequential? %) (mapv f %) %) tree))
|
||||||
|
|
||||||
|
;; Намиране в дърво
|
||||||
|
(defn tree-find [pred tree]
|
||||||
|
(first (filter pred (tree-seq sequential? seq tree))))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Рецепти за управление на състояние
|
||||||
|
|
||||||
|
### 3.1 Service модел с Atoms
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defprotocol Service
|
||||||
|
(start [this])
|
||||||
|
(stop [this])
|
||||||
|
(process [this input]))
|
||||||
|
|
||||||
|
(defn make-service [config]
|
||||||
|
(let [state (atom {:config config
|
||||||
|
:running false
|
||||||
|
:cache {}})]
|
||||||
|
(reify
|
||||||
|
Service
|
||||||
|
(start [_]
|
||||||
|
(swap! state assoc :running true))
|
||||||
|
(stop [_]
|
||||||
|
(swap! state assoc :running false))
|
||||||
|
(process [_ input]
|
||||||
|
(when-not (:running @state)
|
||||||
|
(throw (ex-info "Service не е стартиран" {})))
|
||||||
|
(if-let [cached (get-in @state [:cache input])]
|
||||||
|
cached
|
||||||
|
(let [result (compute input)]
|
||||||
|
(swap! state assoc-in [:cache input] result)
|
||||||
|
result))))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Event Sourcing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn make-event-store []
|
||||||
|
(let [events (atom [])
|
||||||
|
snapshots (atom {})]
|
||||||
|
(reify
|
||||||
|
Object
|
||||||
|
(toString [_] (pr-str @events))
|
||||||
|
clojure.core.protocols/Coll
|
||||||
|
(coll [_] (seq @events))
|
||||||
|
clojure.lang.IFn
|
||||||
|
(invoke [_ event]
|
||||||
|
(let [new-state (apply-event @snapshots event)]
|
||||||
|
(swap! events conj event)
|
||||||
|
(when (seq? new-state)
|
||||||
|
(reset! snapshots new-state))))
|
||||||
|
(invoke [_ n]
|
||||||
|
(get @snapshots n)))))
|
||||||
|
|
||||||
|
(defn apply-event [state event]
|
||||||
|
(case (:type event)
|
||||||
|
:created (assoc state (:id event) (:data event))
|
||||||
|
:updated (update state (:id event) merge (:data event))
|
||||||
|
:deleted (dissoc state (:id event))
|
||||||
|
state))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Cooldown механизъм
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn make-cooldown [timeout-ms]
|
||||||
|
(let [last-call (atom 0)]
|
||||||
|
(fn []
|
||||||
|
(let [now (System/currentTimeMillis)]
|
||||||
|
(when (> (- now @last-call) timeout-ms)
|
||||||
|
(reset! last-call now)
|
||||||
|
true)))))
|
||||||
|
|
||||||
|
(def rate-limiter (make-cooldown 1000))
|
||||||
|
|
||||||
|
;; Употреба
|
||||||
|
(when (rate-limiter)
|
||||||
|
(do-something))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Circuit Breaker
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn make-circuit-breaker [failure-threshold reset-timeout]
|
||||||
|
(let [state (atom {:status :closed
|
||||||
|
:failures 0
|
||||||
|
:last-failure 0})]
|
||||||
|
(fn [f]
|
||||||
|
(let [current @state]
|
||||||
|
(case (:status current)
|
||||||
|
:open
|
||||||
|
(if (> (- (System/currentTimeMillis) (:last-failure current))
|
||||||
|
reset-timeout)
|
||||||
|
(do (swap! state assoc :status :half-open)
|
||||||
|
(try
|
||||||
|
(let [result (f)]
|
||||||
|
(swap! state assoc :status :closed :failures 0)
|
||||||
|
result)
|
||||||
|
(catch Exception e
|
||||||
|
(swap! state assoc :status :open :last-failure (System/currentTimeMillis))
|
||||||
|
(throw e))))
|
||||||
|
(throw (ex-info "Circuit open" {})))
|
||||||
|
:half-open
|
||||||
|
(try
|
||||||
|
(let [result (f)]
|
||||||
|
(swap! state assoc :status :closed :failures 0)
|
||||||
|
result)
|
||||||
|
(catch Exception e
|
||||||
|
(swap! state assoc :status :open :last-failure (System/currentTimeMillis)
|
||||||
|
)
|
||||||
|
(throw e)))
|
||||||
|
:closed
|
||||||
|
(try
|
||||||
|
(let [result (f)]
|
||||||
|
(swap! state assoc :failures 0)
|
||||||
|
result)
|
||||||
|
(catch Exception e
|
||||||
|
(let [failures (inc (:failures current))]
|
||||||
|
(swap! state assoc :failures failures
|
||||||
|
:last-failure (System/currentTimeMillis))
|
||||||
|
(when (>= failures failure-threshold)
|
||||||
|
(swap! state assoc :status :open))
|
||||||
|
(throw e)))))))))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Async рецепти
|
||||||
|
|
||||||
|
### 4.1 Channel Pipeline
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn channel-pipeline [in-f out-f & channels]
|
||||||
|
(doseq [ch channels]
|
||||||
|
(async/go
|
||||||
|
(loop []
|
||||||
|
(when-let [value (<! ch)]
|
||||||
|
(out-f value)
|
||||||
|
(recur))))))
|
||||||
|
|
||||||
|
;; Употреба
|
||||||
|
(let [input-chan (async/chan 100)
|
||||||
|
process-chan (async/chan 100)
|
||||||
|
output-chan (async/chan 100)]
|
||||||
|
(async/pipeline 10 process-chan (map process-item) input-chan)
|
||||||
|
(async/pipeline 10 output-chan (map format-output) process-chan))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Multiplexing канали
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn multiplex [in-chan & out-chans]
|
||||||
|
(async/go
|
||||||
|
(loop [value (<! in-chan)]
|
||||||
|
(when-not (nil? value)
|
||||||
|
(doseq [ch out-chans]
|
||||||
|
(>! ch value))
|
||||||
|
(recur (<! in-chan))))))
|
||||||
|
|
||||||
|
;; Употреба
|
||||||
|
(let [input (async/chan)
|
||||||
|
out1 (async/chan)
|
||||||
|
out2 (async/chan)]
|
||||||
|
(multiplex input out1 out2)
|
||||||
|
;; Сега input се broadcast-ва към двата изхода
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Timeout модели
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Timeout на операция
|
||||||
|
(async/go
|
||||||
|
(let [result (async/alts!! [work-chan
|
||||||
|
(async/timeout 5000)])]
|
||||||
|
(if (= result :timed-out)
|
||||||
|
{:status :timeout}
|
||||||
|
{:status :success :value (first result)})))
|
||||||
|
|
||||||
|
;; Retry с exponential backoff
|
||||||
|
(defn with-retry [f max-attempts delay-ms]
|
||||||
|
(async/go
|
||||||
|
(loop [attempts 0]
|
||||||
|
(let [result (async/<! (async/timeout delay-ms))]
|
||||||
|
(if (= :failure result)
|
||||||
|
(if (< attempts max-attempts)
|
||||||
|
(recur (inc attempts))
|
||||||
|
{:status :failed})
|
||||||
|
{:status :success :value result})))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Windowing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn windowed [in size overlap]
|
||||||
|
(let [out (async/chan)]
|
||||||
|
(async/go
|
||||||
|
(loop [window (vec (take size (async/<! in)))]
|
||||||
|
(when (seq window)
|
||||||
|
(>! out window)
|
||||||
|
(let [next-items (vec (take overlap (rest window)))
|
||||||
|
remaining (- size overlap)
|
||||||
|
new-items (vec (take remaining (async/<! in)))]
|
||||||
|
(recur (into next-items new-items))))))
|
||||||
|
out))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Рецепти за валидация
|
||||||
|
|
||||||
|
### 5.1 Многоетапна валидация
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn validate-stages [data & validators]
|
||||||
|
(reduce
|
||||||
|
(fn [result validator]
|
||||||
|
(let [errors (validator result)]
|
||||||
|
(if (seq errors)
|
||||||
|
(reduced {:status :error :errors errors})
|
||||||
|
result)))
|
||||||
|
{:status :ok :data data}
|
||||||
|
validators))
|
||||||
|
|
||||||
|
(defn non-blank [field]
|
||||||
|
(fn [result]
|
||||||
|
(when (clojure.string/blank? (get-in result [:data field]))
|
||||||
|
[field "не може да е празно"])))
|
||||||
|
|
||||||
|
(defn max-length [field length]
|
||||||
|
(fn [result]
|
||||||
|
(when (> (count (get-in result [:data field])) length)
|
||||||
|
[field (str "трябва да е най-много" length "символа")])))
|
||||||
|
|
||||||
|
(validate-stages
|
||||||
|
{:name ""}
|
||||||
|
(non-blank :name)
|
||||||
|
(max-length :name 50))
|
||||||
|
;; => {:status :error :errors [:name "не може да е празно"]}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Schema валидация
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def email-regex #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
|
||||||
|
|
||||||
|
(defn validate-email [email]
|
||||||
|
(when-not (re-find email-regex email)
|
||||||
|
"Невалиден email формат"))
|
||||||
|
|
||||||
|
(defn validate-user [user]
|
||||||
|
(reduce-kv
|
||||||
|
(fn [errors field validate]
|
||||||
|
(if-let [error (validate (get user field))]
|
||||||
|
(conj errors [field error])
|
||||||
|
errors))
|
||||||
|
[]
|
||||||
|
{:name [#(when (clojure.string/blank? %) "Името е задължително")]
|
||||||
|
:email [validate-email
|
||||||
|
#(when (> (count %) 100) "Email е твърде дълъг")]
|
||||||
|
:age [#(when (or (nil? %) (neg? %)) "Възрастта трябва да е положителна")]}))
|
||||||
|
|
||||||
|
(validate-user {:name "John" :email "john@example.com" :age 30})
|
||||||
|
;; => []
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Contract тестване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defmacro defcontract [name input-spec output-spec & body]
|
||||||
|
`(defn ~name [& args#]
|
||||||
|
(let [input# (first args#)
|
||||||
|
output# (apply ~(into `fn input-spec `@body) args#)]
|
||||||
|
(when-not (~output-spec output#)
|
||||||
|
(throw (ex-info "Нарушение на контракт"
|
||||||
|
{:function '~name
|
||||||
|
:input input#
|
||||||
|
:output output#})))
|
||||||
|
output#)))
|
||||||
|
|
||||||
|
;; Употреба
|
||||||
|
(defcontract add-positive
|
||||||
|
[a number? b number?] ;; Input spec
|
||||||
|
number? ;; Output spec
|
||||||
|
[a b]
|
||||||
|
(+ a b))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Модели за дизайн на API
|
||||||
|
|
||||||
|
### 6.1 Ring Handlers (Чисти функции)
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn wrap-logging [handler]
|
||||||
|
(fn [request]
|
||||||
|
(println "Request:" (:uri request))
|
||||||
|
(let [response (handler request)]
|
||||||
|
(println "Response:" (:status response))
|
||||||
|
response)))
|
||||||
|
|
||||||
|
(defn wrap-cors [handler]
|
||||||
|
(fn [request]
|
||||||
|
(let [response (handler request)]
|
||||||
|
(assoc response :headers
|
||||||
|
(merge (:headers response {})
|
||||||
|
{"Access-Control-Allow-Origin" "*"})))))
|
||||||
|
|
||||||
|
;; Чист handler
|
||||||
|
(defn handle-get-user [request]
|
||||||
|
{:status 200
|
||||||
|
:headers {"Content-Type" "application/json"}
|
||||||
|
:body (pr-str {:name "John" :email "john@example.com"})})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Middleware Stack
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn apply-middleware [handler middlewares]
|
||||||
|
(reduce
|
||||||
|
(fn [h middleware]
|
||||||
|
(middleware h))
|
||||||
|
handler
|
||||||
|
middlewares))
|
||||||
|
|
||||||
|
(def app
|
||||||
|
(-> handler-get-user
|
||||||
|
(apply-middleware [wrap-cors
|
||||||
|
wrap-logging
|
||||||
|
wrap-auth])))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Дефиниции на routes
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def routes
|
||||||
|
[[:get "/users" list-users]
|
||||||
|
[:get "/users/:id" get-user]
|
||||||
|
[:post "/users" create-user]
|
||||||
|
[:put "/users/:id" update-user]
|
||||||
|
[:delete "/users/:id" delete-user]])
|
||||||
|
|
||||||
|
(defn match-route [method path]
|
||||||
|
(some
|
||||||
|
(fn [[m p handler]]
|
||||||
|
(when (and (= method m)
|
||||||
|
(re-matches (route->regex p) path))
|
||||||
|
{:handler handler
|
||||||
|
:params (extract-params p path)}))
|
||||||
|
routes))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 Error Handling Middleware
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn wrap-exception [handler]
|
||||||
|
(fn [request]
|
||||||
|
(try
|
||||||
|
(handler request)
|
||||||
|
(catch Exception e
|
||||||
|
{:status 500
|
||||||
|
:headers {"Content-Type" "application/json"}
|
||||||
|
:body (pr-str {:error (ex-message e)
|
||||||
|
:data (ex-data e)})}))))
|
||||||
|
|
||||||
|
(defn wrap-not-found [handler]
|
||||||
|
(fn [request]
|
||||||
|
(let [response (handler request)]
|
||||||
|
(if (= (:status response) 404)
|
||||||
|
{:status 404
|
||||||
|
:body "Не е намерен"}
|
||||||
|
response))))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Рецепти за тестване
|
||||||
|
|
||||||
|
### 7.1 Property-Based тестване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defcommutative +
|
||||||
|
[a integer? b integer?]
|
||||||
|
(= (+ a b) (+ b a)))
|
||||||
|
|
||||||
|
(defassociative +
|
||||||
|
[a integer? b integer? c integer?]
|
||||||
|
(= (+ (+ a b) c) (+ a (+ b c))))
|
||||||
|
|
||||||
|
;; Идемпотентни операции
|
||||||
|
(defidempotent conj
|
||||||
|
[coll vector? item any?]
|
||||||
|
(= (conj (conj coll item) item)
|
||||||
|
(conj coll item)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Test Fixtures с Random Data
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn with-sample-data [f]
|
||||||
|
(let [samples (gen/sample (s/gen ::user) 10)]
|
||||||
|
(doseq [sample samples]
|
||||||
|
(f sample))))
|
||||||
|
|
||||||
|
(t/use-fixtures :each with-sample-data)
|
||||||
|
|
||||||
|
(t/deftest user-validation-test
|
||||||
|
[sample]
|
||||||
|
(t/is (nil? (validate-user sample))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Mutation тестване
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Просто mutation тестване
|
||||||
|
(defn mutate-and-test [original-fn test-fn mutation]
|
||||||
|
(let [mutated (mutation original-fn)]
|
||||||
|
(try
|
||||||
|
(test-fn mutated)
|
||||||
|
false ;; Тестът премина при мутация = лошо
|
||||||
|
(catch AssertionError _
|
||||||
|
true)))) ;; Тестът хвана мутацията = добро
|
||||||
|
|
||||||
|
;; Генератор на случайни мутации
|
||||||
|
(defn random-mutation [f]
|
||||||
|
(let [mutations [(fn [x] (inc x))
|
||||||
|
(fn [x] (dec x))
|
||||||
|
(fn [x] (* x 2))]]
|
||||||
|
(some #(% f) mutations)))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Рецепти за производителност
|
||||||
|
|
||||||
|
### 8.1 Batch обработка
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn batch-process [items batch-size f]
|
||||||
|
(into []
|
||||||
|
(mapcat f)
|
||||||
|
(partition-all batch-size items)))
|
||||||
|
|
||||||
|
;; Употреба
|
||||||
|
(batch-process (range 10000) 100
|
||||||
|
(fn [batch]
|
||||||
|
(mapv expensive-operation batch)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Caching с TTL
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn make-ttl-cache [ttl-ms]
|
||||||
|
(let [cache (atom {})
|
||||||
|
cleanup (fn []
|
||||||
|
(let [now (System/currentTimeMillis)]
|
||||||
|
(swap! cache
|
||||||
|
(fn [m]
|
||||||
|
(into {}
|
||||||
|
(filter #(< (- now (val %)) ttl-ms))
|
||||||
|
m)))))]
|
||||||
|
(fn [f]
|
||||||
|
(fn [k]
|
||||||
|
(cleanup)
|
||||||
|
(if-let [entry (get @cache k)]
|
||||||
|
(val entry)
|
||||||
|
(let [result (f k)]
|
||||||
|
(swap! cache assoc k [(System/currentTimeMillis) result])
|
||||||
|
result))))))
|
||||||
|
|
||||||
|
(def cached-heavy-operation (make-ttl-cache 60000) heavy-operation)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 Lazy File Processing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn lazy-file-lines [filepath]
|
||||||
|
(line-seq (clojure.java.io/reader filepath)))
|
||||||
|
|
||||||
|
(defn lazy-csv-rows [filepath]
|
||||||
|
(map #(clojure.string/split % #",")
|
||||||
|
(lazy-file-lines filepath)))
|
||||||
|
|
||||||
|
;; Обработка на огромни файлове ред по ред
|
||||||
|
(into []
|
||||||
|
(comp
|
||||||
|
(drop 1) ;; Пропускане на хедър
|
||||||
|
(map #(update % 2 parse-long)) ;; Трансформиране на колона
|
||||||
|
(filter #(= "active" (% 3))))
|
||||||
|
(take 1000 (lazy-csv-rows "large-file.csv")))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 Паралелна обработка на колекции
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn parallel-map [f coll n]
|
||||||
|
(let [parts (partition-all (/ (count coll) n) coll)
|
||||||
|
results (pmap #(doall (map f %)) parts)]
|
||||||
|
(apply concat results)))
|
||||||
|
|
||||||
|
;; С reducers за по-добра производителност
|
||||||
|
(require '[clojure.core.reducers :as r])
|
||||||
|
|
||||||
|
(defn parallel-reduce [f init coll]
|
||||||
|
(r/fold (/ (count coll) 4) f coll))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Чист Clojure: Практически рецепти*
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
# Clojure/Nim: Native Clojure
|
||||||
|
|
||||||
|
> Същият език, който обичаш. Компилиран до нативен код. Без JVM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Какво е Clojure/Nim?
|
||||||
|
|
||||||
|
**Clojure/Nim** е пълен диалект на Clojure, който се компилира до нативен машинен код през Nim компилатора. Това не е интерпретатор — това е истински компилатор с пълен pipeline за оптимизация:
|
||||||
|
|
||||||
|
```
|
||||||
|
Твоят .clj файл
|
||||||
|
↓
|
||||||
|
Reader (EDN парсер)
|
||||||
|
↓
|
||||||
|
Разширяване на макроси (defmacro, syntax-quote, ->, ->>)
|
||||||
|
↓
|
||||||
|
Emitter (Clojure AST → Nim изходен код)
|
||||||
|
↓
|
||||||
|
Nim компилатор → C код
|
||||||
|
↓
|
||||||
|
C компилатор → Нативен бинарен файл
|
||||||
|
```
|
||||||
|
|
||||||
|
Резултатът е един изпълним файл, често под **1 MB**, който стартира мигновено — **без JVM warmup**.
|
||||||
|
|
||||||
|
### Защо native?
|
||||||
|
|
||||||
|
| JVM Clojure | Clojure/Nim |
|
||||||
|
|-------------|-------------|
|
||||||
|
| Нуждае се от Java runtime | Самостоятелен бинарен файл |
|
||||||
|
| ~2-5 секунди startup | Мигновен старт |
|
||||||
|
| ~100-300 MB RAM | ~1-10 MB |
|
||||||
|
| JIT паузи | Ahead-of-time, предсказуем |
|
||||||
|
| Идеален за сървъри | Идеален за CLI, embedded, WASM |
|
||||||
|
|
||||||
|
Това не е играчка. Това е production компилатор с **276+ теста**, **persistent структури от данни (HAMT)**, **core.async канали** и **AI-асистиран workflow**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Инсталация
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitlab.com/balvatar/lisp-nim.git
|
||||||
|
cd lisp-nim
|
||||||
|
make build
|
||||||
|
make check # всички тестове + примери
|
||||||
|
```
|
||||||
|
|
||||||
|
**Изисквания:** Nim ≥ 2.0, GCC или Clang, make.
|
||||||
|
|
||||||
|
Толкова. Няма Java. Няма Leiningen. Няма deps, които се свалят по десет минути.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Първа програма
|
||||||
|
|
||||||
|
Създай `hello.clj`:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(println "Здравей от native Clojure!")
|
||||||
|
(println (+ 1 2 3 4 5))
|
||||||
|
```
|
||||||
|
|
||||||
|
Изпълни:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./cljnim run hello.clj
|
||||||
|
Здравей от native Clojure!
|
||||||
|
15
|
||||||
|
```
|
||||||
|
|
||||||
|
Забележи: програмата беше парсирана, разширена с макроси, емитирана като Nim, компилирана до C, компилирана до машинен код и изпълнена — всичко за под секунда.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## REPL: Два режима
|
||||||
|
|
||||||
|
### Човешки REPL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./cljnim repl
|
||||||
|
user> (defn square [x] (* x x))
|
||||||
|
user> (square 7)
|
||||||
|
=> 49
|
||||||
|
user> (atom 42)
|
||||||
|
=> (atom 42)
|
||||||
|
user> :ai функция за фибоначи
|
||||||
|
🤖 Мисля...
|
||||||
|
💡 AI Предложение:
|
||||||
|
(defn fib [n]
|
||||||
|
(loop [a 0 b 1 i 0]
|
||||||
|
(if (= i n) a (recur b (+ a b) (inc i)))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON REPL (за AI агенти)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./cljnim repl --json
|
||||||
|
{"status":"ready","ns":"user","mode":"json"}
|
||||||
|
|
||||||
|
> {"op":"eval","form":"(+ 1 2 3)"}
|
||||||
|
{"status":"ok","result":{"printed":"6"},"meta":{"ms":12}}
|
||||||
|
|
||||||
|
> {"tool":"cljnim/eval","args":{"form":"(defn greet [name] (str \"Hello \" name))"}}
|
||||||
|
{"status":"ok","result":{"type":"var","name":"greet"},...}
|
||||||
|
```
|
||||||
|
|
||||||
|
JSON REPL е проектиран за програмно взаимодействие. Всяка операция има структуриран вход и изход. AI агенти могат да откриват възможности, оценяват код, инспектират дефиниции и обработват batch форми без парсиране на човешки текст.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AI-асистирана разработка
|
||||||
|
|
||||||
|
Clojure/Nim е първата Clojure имплементация, в която AI асистенцията е първокласна функция.
|
||||||
|
|
||||||
|
### Обяснение на грешки
|
||||||
|
|
||||||
|
Когато компилацията fail-не, компилаторът пита AI за помощ:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./cljnim run broken.clj
|
||||||
|
Compilation failed
|
||||||
|
Error: identifier expected, but got 'keyword var'
|
||||||
|
|
||||||
|
💡 AI Предложение:
|
||||||
|
Грешката е защото `var` е запазена дума в Nim.
|
||||||
|
Оправи: Преименувай функцията на `my-var`.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Генериране на код
|
||||||
|
|
||||||
|
Генерирай идиоматичен Clojure от описание:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./cljnim ai "функция, която филтрира четните числа"
|
||||||
|
(defn filter-even [coll]
|
||||||
|
(loop [remaining coll result []]
|
||||||
|
(if (empty? remaining)
|
||||||
|
result
|
||||||
|
(let [x (first remaining)]
|
||||||
|
(recur (rest remaining)
|
||||||
|
(if (even? x) (conj result x) result))))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Настройка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export DEEPSEEK_API_KEY="sk-..."
|
||||||
|
# или OPENAI_API_KEY, или MIMO_API_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `loop` / `recur`: Истинска TCO
|
||||||
|
|
||||||
|
За разлика от JVM Clojure (който използва `recur`, за да избегне stack overflow, но все пак работи върху stack-based VM), Clojure/Nim компилира `loop`/`recur` до native `while` цикъл:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn factorial [n]
|
||||||
|
(loop [acc 1 i n]
|
||||||
|
(if (= i 0)
|
||||||
|
acc
|
||||||
|
(recur (* acc i) (dec i)))))
|
||||||
|
```
|
||||||
|
|
||||||
|
Това генерира ефективен C код без функционални извиквания. **Истински O(1) stack space**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cross-Compilation Target-и
|
||||||
|
|
||||||
|
Clojure/Nim може да таргетира множество платформи от един и същи изходен код:
|
||||||
|
|
||||||
|
### Нативен бинарен файл (по подразбиране)
|
||||||
|
```bash
|
||||||
|
./cljnim run program.clj
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript
|
||||||
|
```bash
|
||||||
|
./cljnim compile program.clj program.nim
|
||||||
|
nim js -o:program.js program.nim
|
||||||
|
node program.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### C Shared Library
|
||||||
|
```bash
|
||||||
|
./cljnim compile-lib program.clj program.nim
|
||||||
|
nim c --app:lib -o:libprogram.so program.nim
|
||||||
|
```
|
||||||
|
|
||||||
|
### WASM (експериментално)
|
||||||
|
```bash
|
||||||
|
nim c -d:release --cpu:wasm32 --os:linux program.nim
|
||||||
|
```
|
||||||
|
|
||||||
|
Това е нещо, което JVM Clojure просто не може да направи.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Persistent Структури от Данни
|
||||||
|
|
||||||
|
Clojure/Nim имплементира истински **Hash Array Mapped Trie (HAMT)** вектори и карти:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def v (vector 1 2 3))
|
||||||
|
(def v2 (conj v 4))
|
||||||
|
;; v => [1 2 3]
|
||||||
|
;; v2 => [1 2 3 4]
|
||||||
|
;; Structural sharing: O(log₃₂ n) обновления, не O(n) копия
|
||||||
|
```
|
||||||
|
|
||||||
|
Runtime-ът използва същите алгоритми като Clojure/JVM (32-way branching, path copying, tail optimization), но компилиран до bare metal.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Конкурентност
|
||||||
|
|
||||||
|
### Atoms
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def counter (atom 0))
|
||||||
|
(swap! counter inc) ;; => 1
|
||||||
|
(reset! counter 100) ;; => 100
|
||||||
|
(deref counter) ;; => 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### Agents
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def state (agent 0))
|
||||||
|
(send state + 10)
|
||||||
|
(await state)
|
||||||
|
(deref state) ;; => 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Канали (core.async)
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def ch (chan 10))
|
||||||
|
(put! ch 42)
|
||||||
|
(take! ch) ;; => 42
|
||||||
|
(close! ch)
|
||||||
|
```
|
||||||
|
|
||||||
|
Всички примитиви за конкурентност работят както в компилирания runtime, така и в in-memory интерпретатора.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Макроси, които работят
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defmacro unless [condition body]
|
||||||
|
`(if (not ~condition)
|
||||||
|
~body))
|
||||||
|
|
||||||
|
(unless false
|
||||||
|
(println "Това се печата!"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Threading макроси, `when-let`, `cond`, `doto`, `some->` — всички имплементирани като истински Clojure макроси, разширявани по време на компилация.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Кога да използваш Clojure/Nim
|
||||||
|
|
||||||
|
| Use Case | Clojure/JVM | Clojure/Nim |
|
||||||
|
|----------|-------------|-------------|
|
||||||
|
| Големи уеб услуги | ✅ | ⚠️ (ранен етап) |
|
||||||
|
| CLI инструменти | ⚠️ (бавен старт) | ✅ (мигновен) |
|
||||||
|
| Embedded системи | ❌ | ✅ |
|
||||||
|
| WASM / браузър | ClojureScript | ✅ (native WASM) |
|
||||||
|
| Споделени библиотеки | ❌ | ✅ |
|
||||||
|
| AI agent scripting | ❌ | ✅ (JSON REPL) |
|
||||||
|
| Учене на Clojure | ✅ | ✅ (без JVM) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Допълнително четене
|
||||||
|
|
||||||
|
- [Основи](01-fundamentals.md) — Core Clojure концепции (валидни за всички диалекти)
|
||||||
|
- [Архитектура](../../docs/bg/02-architecture.md) — Как работи компилаторът вътрешно
|
||||||
|
- [AI Интеграция](../../docs/bg/03-ai-integration.md) — Детайли за AI функциите
|
||||||
|
- [API Справочник](../../docs/bg/04-api-reference.md) — Спецификация на JSON REPL протокола
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Clojure/Nim е доказателство, че не ти трябва виртуална машина, за да пишеш елегантен, функционален, immutable код. Трябва ти само добър компилатор.*
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
# Subject Index - Bulgarian
|
||||||
|
|
||||||
|
## А
|
||||||
|
|
||||||
|
- **Агенти (Agents)** - [11.3](bg/01-fundamentals.md#113-агенти-agents)
|
||||||
|
- **Алиасиране на namespaces** - [9.3](bg/01-fundamentals.md#93-отнасяне-и-импортиране)
|
||||||
|
- **Алокиране (памет)** - [8.2](bg/02-advanced.md#82-transient-структури-от-данни)
|
||||||
|
- **Анонимни функции** - [5.2](bg/01-fundamentals.md#52-анонимни-функции)
|
||||||
|
- **Аритности** - [5.1.2](bg/01-fundamentals.md#512-множество-арности)
|
||||||
|
- **Асинхронно програмиране** - [16](bg/01-fundamentals.md#16-coreasync), [4](bg/04-recipes.md#4-async-рецепти)
|
||||||
|
- **Атом (Atom)** - [11.1](bg/01-fundamentals.md#11-атоми-atoms), [5](bg/05-clojure-nim.md#конкурентност)
|
||||||
|
- **AI интеграция** - [5](bg/05-clojure-nim.md#ai-асистирана-разработка)
|
||||||
|
- **AI агенти** - [5](bg/05-clojure-nim.md#json-repl-за-ai-агенти)
|
||||||
|
- **Clojure/Nim** - [5](bg/05-clojure-nim.md) — Нативна компилация, AI, JSON REPL
|
||||||
|
- **Cross-compilation** - [5](bg/05-clojure-nim.md#cross-compilation-target-и) — JS, WASM, shared libraries
|
||||||
|
|
||||||
|
## Б
|
||||||
|
|
||||||
|
- **Батч обработка** - [8.5](bg/02-advanced.md#85-batch-обработка), [8.1](bg/04-recipes.md#81-batch-обработка)
|
||||||
|
- **Булеви стойности** - [4.4](bg/01-fundamentals.md#44-булеви-стойности)
|
||||||
|
|
||||||
|
## В
|
||||||
|
|
||||||
|
- **Вариадични функции** - [1.1](bg/02-advanced.md#11-вариадични-функции)
|
||||||
|
- **Вграждане на типове** - [12.3](bg/01-fundamentals.md#123-разширяване-на-съществуващи-типове)
|
||||||
|
- **Вектори (Vectors)** - [4.8](bg/01-fundamentals.md#48-вектори-vectors)
|
||||||
|
- **Вербална форма** - [3.6](bg/01-fundamentals.md#36-интервали-и-форматиране)
|
||||||
|
- **Взаимна рекурсия** - [1.4](bg/02-advanced.md#14-взаимна-рекурсия)
|
||||||
|
- **Видове диспечиране** - [13](bg/01-fundamentals.md#13-многометодни-функции)
|
||||||
|
- **Вложен достъп** - [2.1](bg/04-recipes.md#21-вложен-достъп-до-данни)
|
||||||
|
- **Вложени серии** - [2.3](bg/02-advanced.md#23-lazy-cons-и-реализация)
|
||||||
|
- **Всички (reduce)** - [7.3.3](bg/01-fundamentals.md#733-reduce)
|
||||||
|
- **Входни/изходни условия** - [1.6](bg/02-advanced.md#16-пред--и-пост-условия)
|
||||||
|
|
||||||
|
## Г
|
||||||
|
|
||||||
|
- **Генеративно тестване** - [14.4](bg/01-fundamentals.md#144-генеративно-тестване), [4.5](bg/02-advanced.md#45-генеративно-тестване)
|
||||||
|
- **Групиране** - [2.2](bg/04-recipes.md#22-групиране-и-агрегиране)
|
||||||
|
|
||||||
|
## Д
|
||||||
|
|
||||||
|
- **Дата структури** - [4](bg/01-fundamentals.md#4-структури-от-данни)
|
||||||
|
- **дебъг** - [8.5](bg/03-tooling.md#85-breakpoints)
|
||||||
|
- **Дебъгване** - [8](bg/03-tooling.md#8-техники-за-дебъгване)
|
||||||
|
- **def** - [3.3.1](bg/01-fundamentals.md#331-def)
|
||||||
|
- **defmacro** - [10.1](bg/01-fundamentals.md#101-какво-са-макросите)
|
||||||
|
- **defmethod** - [13.1](bg/01-fundamentals.md#131-дефиниране-на-многометодни-функции)
|
||||||
|
- **defmulti** - [13.1](bg/01-fundamentals.md#131-дефиниране-на-многометодни-функции)
|
||||||
|
- **defn** - [5.1.1](bg/01-fundamentals.md#511-основен-синтаксис)
|
||||||
|
- **defprotocol** - [12.1](bg/01-fundamentals.md#121-протоколи)
|
||||||
|
- **defrecord** - [12.2](bg/01-fundamentals.md#122-записи)
|
||||||
|
- **delay** - [11.6](bg/01-fundamentals.md#116-promises-и-delivered)
|
||||||
|
- **Деструктуриране** - [8](bg/01-fundamentals.md#8-деструктуриране)
|
||||||
|
- **диспечиране по поведение** - [13](bg/01-fundamentals.md#13-многометодни-функции)
|
||||||
|
- **Docker** - [6.3](bg/03-tooling.md#63-docker-интеграция)
|
||||||
|
- **doseq** - [6.2.4](bg/01-fundamentals.md#624-doseq-странични-ефекти)
|
||||||
|
- **dosync** - [11.2](bg/01-fundamentals.md#112-референции-refs)
|
||||||
|
- **dotimes** - [6.2.3](bg/01-fundamentals.md#623-for-list-comprehension)
|
||||||
|
- **drop** - [7.3.6](bg/01-fundamentals.md#736-take--drop)
|
||||||
|
- **drop-while** - [7.3.6](bg/01-fundamentals.md#736-take--drop)
|
||||||
|
|
||||||
|
## Е
|
||||||
|
|
||||||
|
- **Екосистема от библиотеки** - [7](bg/03-tooling.md#7-екосистема-от-библиотеки)
|
||||||
|
- **Едноредови коментари** - [3.5](bg/01-fundamentals.md#35-коментари)
|
||||||
|
- **Either модел** - [1.2](bg/04-recipes.md#12-eitherresult-модел)
|
||||||
|
|
||||||
|
## Ж
|
||||||
|
|
||||||
|
- **Жизнения цикъл** - [3.1](bg/04-recipes.md#31-service-модел-с-atoms)
|
||||||
|
|
||||||
|
## З
|
||||||
|
|
||||||
|
- **Затваряне (Closure)** - [5.4](bg/01-fundamentals.md#54-затваряния-closures)
|
||||||
|
- **Записи (Records)** - [12.2](bg/01-fundamentals.md#122-записи)
|
||||||
|
- **Зареждане на зависимости** - [2.1](bg/03-tooling.md#21-depsedn-референция)
|
||||||
|
|
||||||
|
## И
|
||||||
|
|
||||||
|
- **Идентичности** - [11](bg/01-fundamentals.md#11-конкурентност)
|
||||||
|
- **Изграждане и деплойване** - [6](bg/03-tooling.md#6-изграждане-и-деплойване)
|
||||||
|
- **Изключения** - [6.3](bg/01-fundamentals.md#63-обработка-на-изключения)
|
||||||
|
- **Итерация** - [6.2](bg/01-fundamentals.md#62-итерация)
|
||||||
|
- **Инсталация** - [2.1](bg/01-fundamentals.md#21-инсталация)
|
||||||
|
- **Интеграционно тестване** - [3.3](bg/03-tooling.md#33-тестови-namespace-и)
|
||||||
|
- **Инфраструктура за тестване** - [3](bg/03-tooling.md#3-инфраструктура-за-тестване)
|
||||||
|
|
||||||
|
## К
|
||||||
|
|
||||||
|
- **Канали (Channels)** - [16.1](bg/01-fundamentals.md#161-канали)
|
||||||
|
- **Кеширане** - [1.5](bg/01-fundamentals.md#15-мемоизация), [8.6](bg/02-advanced.md#86-предварително-зареждане-и-кеширане), [8.2](bg/04-recipes.md#82-caching-с-ttl)
|
||||||
|
- **Ключови думи (Keywords)** - [4.5](bg/01-fundamentals.md#45-ключови-думи-keywords)
|
||||||
|
- **Колекции** - [4.12](bg/01-fundamentals.md#412-библиотека-за-колекции)
|
||||||
|
- **Коментари** - [3.5](bg/01-fundamentals.md#35-коментари)
|
||||||
|
- **Композиране на функции** - [5.3](bg/01-fundamentals.md#53-функции-от-по-висок-ред)
|
||||||
|
- **Конкурентност** - [11](bg/01-fundamentals.md#11-конкурентност)
|
||||||
|
- **Контракти** - [5.3](bg/04-recipes.md#53-contract-тестване)
|
||||||
|
- **Конфигуриране** - [1.3](bg/02-advanced.md#13-аргументи-от-тип-ключова-дума)
|
||||||
|
- **Куриране** - [5.3](bg/04-recipes.md#53-contract-тестване)
|
||||||
|
|
||||||
|
## Л
|
||||||
|
|
||||||
|
- **Лениви серии** - [7.2](bg/01-fundamentals.md#72-мързеливи-серии), [2](bg/02-advanced.md#2-мързеливи-серии---задълбочено)
|
||||||
|
- **let** - [3.3.2](bg/01-fundamentals.md#332-let)
|
||||||
|
- **letfn** - [5.1.3](bg/01-fundamentals.md#513-променлив-брой-аргументи)
|
||||||
|
- **Литери** - [4.1](bg/01-fundamentals.md#41-числа)
|
||||||
|
- **Логване** - [8.6](bg/03-tooling.md#86-logging)
|
||||||
|
- **loop** - [6.2.2](bg/01-fundamentals.md#622-looprecur)
|
||||||
|
|
||||||
|
## М
|
||||||
|
|
||||||
|
- **Макроси** - [10](bg/01-fundamentals.md#10-макроси)
|
||||||
|
- **Мемоизация** - [1.5](bg/02-advanced.md#15-мемоизация)
|
||||||
|
- **Метаданни на функции** - [1.7](bg/02-advanced.md#17-метаданни-на-функции)
|
||||||
|
- **Множества (Sets)** - [4.10](bg/01-fundamentals.md#410-множества-sets)
|
||||||
|
- **Многометодни функции** - [13](bg/01-fundamentals.md#13-многометодни-функции)
|
||||||
|
- **Модел на състояние (State machine)** - [1.3](bg/04-recipes.md#13-state-machine-модел)
|
||||||
|
- **Мързелива консумация** - [2.3](bg/02-advanced.md#23-lazy-cons-и-реализация)
|
||||||
|
|
||||||
|
## Н
|
||||||
|
|
||||||
|
- **Наблюдение на състояние** - [8.4](bg/03-tooling.md#84-watching-state)
|
||||||
|
- **Намиране на библиотеки** - [7.6](bg/03-tooling.md#76-намиране-на-библиотеки)
|
||||||
|
- **Насоки за STM** - [11.7](bg/01-fundamentals.md#117-насоки-за-stm)
|
||||||
|
- **Независимост от типа** - [13.1](bg/01-fundamentals.md#131-дефиниране-на-многометодни-функции)
|
||||||
|
- **Неизменяемост** - [1.2.1](bg/01-fundamentals.md#121-неизменяемост-по-подразбиране), [4](bg/01-fundamentals.md#4-структури-от-данни)
|
||||||
|
- **Низове (Strings)** - [4.2](bg/01-fundamentals.md#42-низове)
|
||||||
|
- **Namespace** - [9](bg/01-fundamentals.md#9-пространства-от-имена)
|
||||||
|
|
||||||
|
## О
|
||||||
|
|
||||||
|
- **Обекти за серии** - [2.4](bg/02-advanced.md#24-seqable-обекти)
|
||||||
|
- **Обхождане на колекции** - [7.5](bg/01-fundamentals.md#75-обхождане-на-колекции)
|
||||||
|
- **Област на видимост** - [3.3.2](bg/01-fundamentals.md#332-let)
|
||||||
|
- **Операции с дървета** - [2.4](bg/04-recipes.md#24-tree-операции)
|
||||||
|
- **Оптимизация на производителността** - [8](bg/02-advanced.md#8-оптимизация-на-производителността)
|
||||||
|
- **Основни функции** - [5](bg/01-fundamentals.md#5-функции)
|
||||||
|
- **Отношения** - [13.3](bg/01-fundamentals.md#133-йерархии)
|
||||||
|
|
||||||
|
## П
|
||||||
|
|
||||||
|
- **Паралелизъм** - [7](bg/02-advanced.md#7-паралелизъм)
|
||||||
|
- **Персонализирани reducers** - [6.2](bg/02-advanced.md#62-използване-на-reducers)
|
||||||
|
- **Писане на редове** - [8.1](bg/03-tooling.md#81-print-дебъгване)
|
||||||
|
- **Предикати** - [4.12](bg/01-fundamentals.md#412-библиотека-за-колекции)
|
||||||
|
- **Предварително зареждане** - [8.6](bg/02-advanced.md#86-предварително-зареждане-и-кеширане)
|
||||||
|
- **Презареждане на код** - [4.1](bg/03-tooling.md#41-repl-driven-разработка), [4.2](bg/03-tooling.md#42-hot-loading-на-код)
|
||||||
|
- **Примитивни типове** - [4.1](bg/01-fundamentals.md#41-числа)
|
||||||
|
- **Проектна структура** - [1](bg/03-tooling.md#1-структура-на-проекта)
|
||||||
|
- **Променливи (Vars)** - [11.4](bg/01-fundamentals.md#114-променливи-vars)
|
||||||
|
- **Протоколи** - [12](bg/01-fundamentals.md#12-протоколи-и-записи)
|
||||||
|
- **Процеси** - [3.1](bg/04-recipes.md#31-service-модел-с-atoms)
|
||||||
|
|
||||||
|
## Р
|
||||||
|
|
||||||
|
- **Разширени функции** - [1](bg/02-advanced.md#1-разширени-функции)
|
||||||
|
- **Редуктори** - [6](bg/02-advanced.md#6-reducibles)
|
||||||
|
- **Референции (Refs)** - [11.2](bg/01-fundamentals.md#112-референции-refs)
|
||||||
|
- **Рецепти за async** - [4](bg/04-recipes.md#4-async-рецепти)
|
||||||
|
- **Рецепти за валидация** - [5](bg/04-recipes.md#5-рецепти-за-валидация)
|
||||||
|
- **Рецепти за данни** - [2](bg/04-recipes.md#2-рецепти-за-трансформация-на-данни)
|
||||||
|
- **Рецепти за производителност** - [8](bg/04-recipes.md#8-рецепти-за-производителност)
|
||||||
|
- **Рецепти за тестване** - [7](bg/04-recipes.md#7-рецепти-за-тестване)
|
||||||
|
- **Рецепти за управление на състояние** - [3](bg/04-recipes.md#3-рецепти-за-управление-на-състояние)
|
||||||
|
- **Резервно копие** - [4.3](bg/04-recipes.md#43-timeout-модели)
|
||||||
|
- **Рекursивност** - [6.2.1](bg/01-fundamentals.md#621-рекурсия)
|
||||||
|
- **REPL** - [2.2](bg/01-fundamentals.md#22-вашият-първи-clojure-проект), [15](bg/01-fundamentals.md#15-repl)
|
||||||
|
- **Роля** - [5.3](bg/04-recipes.md#53-contract-тестване)
|
||||||
|
|
||||||
|
## С
|
||||||
|
|
||||||
|
- **Свободни променливи** - [5.4](bg/01-fundamentals.md#54-затваряния-closures)
|
||||||
|
- **Серии (Sequences)** - [7](bg/01-fundamentals.md#7-серии-и-мързеливо-оценяване)
|
||||||
|
- **Символи (Symbols)** - [4.6](bg/01-fundamentals.md#46-символи-symbols)
|
||||||
|
- **Синтаксис цитат** - [10.2](bg/01-fundamentals.md#102-syntax-quote)
|
||||||
|
- **Специални форми** - [3.3](bg/01-fundamentals.md#33-специални-форми)
|
||||||
|
- **Списъци (Lists)** - [4.7](bg/01-fundamentals.md#47-списъци-lists)
|
||||||
|
- **Спретващо сери** - [2.2](bg/02-advanced.md#22-chunked-серии)
|
||||||
|
- **Средна работа** - [8.5](bg/02-advanced.md#85-batch-обработка)
|
||||||
|
- **Статус на веригата** - [3.4](bg/04-recipes.md#34-circuit-breaker)
|
||||||
|
- **Стек trace** - [8.2](bg/03-tooling.md#82-stack-traces)
|
||||||
|
- **Структури от данни** - [4](bg/01-fundamentals.md#4-структури-от-данни)
|
||||||
|
- **Събития** - [3.2](bg/04-recipes.md#32-event-sourcing)
|
||||||
|
|
||||||
|
## Т
|
||||||
|
|
||||||
|
- **Текстови низове** - [4.2](bg/01-fundamentals.md#42-низове)
|
||||||
|
- **Тестване** - [14](bg/01-fundamentals.md#14-тестване)
|
||||||
|
- **Типове** - [4.1](bg/01-fundamentals.md#41-числа)
|
||||||
|
- **Трансдюсъри** - [3](bg/02-advanced.md#3-трансдюсъри)
|
||||||
|
- **Трансформиране на данни** - [2](bg/04-recipes.md#2-рецепти-за-трансформация-на-данни)
|
||||||
|
|
||||||
|
## У
|
||||||
|
|
||||||
|
- **Управление на състояние** - [3](bg/04-recipes.md#3-рецепти-за-управление-на-състояние)
|
||||||
|
- **Уникални стойности** - [7.3.9](bg/01-fundamentals.md#739-distinct--sort--shuffle)
|
||||||
|
|
||||||
|
## Ф
|
||||||
|
|
||||||
|
- **Фабрични функции** - [12.2](bg/01-fundamentals.md#122-записи)
|
||||||
|
- **Философия на Clojure** - [1.4](bg/01-fundamentals.md#14-философията-на-clojure)
|
||||||
|
- **Филтриране** - [7.3.2](bg/01-fundamentals.md#732-filter--remove)
|
||||||
|
- **Форматиране** - [3.6](bg/01-fundamentals.md#36-интервали-и-форматиране)
|
||||||
|
- **Функции** - [5](bg/01-fundamentals.md#5-функции)
|
||||||
|
- **Функции от по-висок ред** - [5.3](bg/01-fundamentals.md#53-функции-от-по-висок-ред)
|
||||||
|
- **Функционално програмиране** - [1.2.2](bg/01-fundamentals.md#122-функционално-пограмиране)
|
||||||
|
|
||||||
|
## Х
|
||||||
|
|
||||||
|
- **Хигиена на макроси** - [10.7](bg/01-fundamentals.md#107-хгиена)
|
||||||
|
- **Хранилище на събития** - [3.2](bg/04-recipes.md#32-event-sourcing)
|
||||||
|
|
||||||
|
## Ц
|
||||||
|
|
||||||
|
- **Целочислени типове** - [4.1.1](bg/01-fundamentals.md#411-целочислени-типове)
|
||||||
|
- **Цикли** - [6.2](bg/01-fundamentals.md#62-итерация)
|
||||||
|
|
||||||
|
## Ч
|
||||||
|
|
||||||
|
- **Числа** - [4.1](bg/01-fundamentals.md#41-числа)
|
||||||
|
- **Числа с плаваща запетая** - [4.1.2](bg/01-fundamentals.md#412-числа-с-плаваща-запетая)
|
||||||
|
- **Често срещани модели** - [1](bg/04-recipes.md#1-често-срещани-модели)
|
||||||
|
|
||||||
|
## Ш
|
||||||
|
|
||||||
|
- **Шаблони** - [5.2](bg/04-recipes.md#52-schema-валидация)
|
||||||
|
|
||||||
|
## Щ
|
||||||
|
|
||||||
|
- **Щафета (pipeline)** - [4.1](bg/04-recipes.md#41-channel-pipeline)
|
||||||
|
|
||||||
|
## Ъ
|
||||||
|
|
||||||
|
- **Ъндъфи** - [3.2](bg/04-recipes.md#32-event-sourcing)
|
||||||
|
|
||||||
|
## Я
|
||||||
|
|
||||||
|
- **Ядро async** - [16](bg/01-fundamentals.md#16-coreasync)
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,710 @@
|
|||||||
|
# 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*
|
||||||
@@ -0,0 +1,629 @@
|
|||||||
|
# Pure Clojure: Project Structure and Tools
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Project Structure](#1-project-structure)
|
||||||
|
2. [deps.edn and CLI Tools](#2-depsedn-and-cli-tools)
|
||||||
|
3. [Testing Infrastructure](#3-testing-infrastructure)
|
||||||
|
4. [Development Workflow](#4-development-workflow)
|
||||||
|
5. [Code Quality Tools](#5-code-quality-tools)
|
||||||
|
6. [Building and Deployment](#6-building-and-deployment)
|
||||||
|
7. [Library Ecosystem](#7-library-ecosystem)
|
||||||
|
8. [Debugging Techniques](#8-debugging-techniques)
|
||||||
|
9. [Index](#9-index)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Project Structure
|
||||||
|
|
||||||
|
### 1.1 Typical Project Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
myproject/
|
||||||
|
├── deps.edn
|
||||||
|
├── src/
|
||||||
|
│ └── myproject/
|
||||||
|
│ ├── core.clj
|
||||||
|
│ ├── util.clj
|
||||||
|
│ └── spec/
|
||||||
|
│ └── core_spec.clj
|
||||||
|
├── test/
|
||||||
|
│ └── myproject/
|
||||||
|
│ ├── core_test.clj
|
||||||
|
│ └── util_test.clj
|
||||||
|
├── resources/
|
||||||
|
├── doc/
|
||||||
|
│ └── intro.md
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Namespaces as File Paths
|
||||||
|
|
||||||
|
Namespaces map to file paths:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; src/myproject/core.clj
|
||||||
|
(ns myproject.core)
|
||||||
|
|
||||||
|
;; src/myproject/util.clj
|
||||||
|
(ns myproject.util)
|
||||||
|
|
||||||
|
;; src/myproject/spec/user.clj
|
||||||
|
(ns myproject.spec.user)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Multi-module Projects
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; deps.edn with multiple modules
|
||||||
|
{:paths ["src" "modules/common/src"]
|
||||||
|
:deps {org.clojure/clojure {:mvn/version "1.11.1"}}
|
||||||
|
:aliases
|
||||||
|
{:dev {:extra-paths ["test"]
|
||||||
|
:extra-deps {}}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 Source vs Test Separation
|
||||||
|
|
||||||
|
- Source code: `src/` directory
|
||||||
|
- Tests: `test/` directory
|
||||||
|
- Both added to classpath during development
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. deps.edn and CLI Tools
|
||||||
|
|
||||||
|
### 2.1 deps.edn Reference
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}
|
||||||
|
org.clojure/data.json {:mvn/version "2.4.0"}
|
||||||
|
clojure.java-time/clojure.java-time {:mvn/version "1.4.2"}
|
||||||
|
}
|
||||||
|
:paths {:src ["src"]
|
||||||
|
:test ["test"]}
|
||||||
|
:aliases
|
||||||
|
{:test {:extra-paths ["test"]
|
||||||
|
:extra-deps {org.clojure/test.check {:mvn/version "1.1.1"}}}
|
||||||
|
:dev {:jvm-opts ["-Xmx4g"]}
|
||||||
|
:bench {:extra-deps {criterium/criterium {:mvn/version "0.4.4"}}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Running Code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run a script
|
||||||
|
clj -M script.clj
|
||||||
|
|
||||||
|
# Run with test alias
|
||||||
|
clj -M:test -m myproject.test-runner
|
||||||
|
|
||||||
|
# Run REPL with deps
|
||||||
|
clj -M
|
||||||
|
|
||||||
|
# Run with specific deps
|
||||||
|
clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.11.1"}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Understanding Aliases
|
||||||
|
|
||||||
|
Aliases modify classpath or behavior:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test with generative checking
|
||||||
|
clj -M:test:gen
|
||||||
|
|
||||||
|
# Production build
|
||||||
|
clj -M:prod build
|
||||||
|
|
||||||
|
# Dev mode with extra checks
|
||||||
|
clj -M:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Making Dependencies
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; deps.edn
|
||||||
|
{:deps {io.github.clojure/data.json {:git/sha "..."}}}
|
||||||
|
|
||||||
|
;; Classpath
|
||||||
|
clj -Sdescribe # Show effective classpath
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Testing Infrastructure
|
||||||
|
|
||||||
|
### 3.1 test.check for Generative Testing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[clojure.test.check :as tc]
|
||||||
|
'[clojure.test.check.generators :as gen]
|
||||||
|
'[clojure.test.check.properties :as prop])
|
||||||
|
|
||||||
|
(def prop-sort-idempotent
|
||||||
|
(prop/for-all
|
||||||
|
[v (gen/vector gen/int)]
|
||||||
|
(= (sort v) (sort (sort v)))))
|
||||||
|
|
||||||
|
(tc/quick-check 100 prop-sort-idempotent)
|
||||||
|
;; => {:result true, :pass? true, ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Fixtures for Setup/Teardown
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myproject.test-util
|
||||||
|
(:require [clojure.test :as t]
|
||||||
|
[myproject.db :as db]))
|
||||||
|
|
||||||
|
(defn with-test-db [f]
|
||||||
|
(db/connect! :test)
|
||||||
|
(f)
|
||||||
|
(db/disconnect!))
|
||||||
|
|
||||||
|
(defn with-logging [f]
|
||||||
|
(println "Before test")
|
||||||
|
(f)
|
||||||
|
(println "After test"))
|
||||||
|
|
||||||
|
;; Apply fixtures
|
||||||
|
(t/use-fixtures :each with-test-db with-logging)
|
||||||
|
(t/use-fixtures :once db/setup-once)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Test Namespaces
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myproject.core-test
|
||||||
|
(:require [clojure.test :as t]
|
||||||
|
[myproject.core :as core]))
|
||||||
|
|
||||||
|
(t/deftest ^:integration api-test
|
||||||
|
"Integration tests for external API"
|
||||||
|
...)
|
||||||
|
|
||||||
|
;; Run specific tests
|
||||||
|
(t/run-tests 'myproject.core-test)
|
||||||
|
(t/run-tests #"myproject.*test")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Expectations-style Testing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Using expectations library
|
||||||
|
(require '[expectations :refer [expect]])
|
||||||
|
|
||||||
|
(expect 4 (+ 2 2))
|
||||||
|
(expect ArithmeticException (/ 1 0))
|
||||||
|
(expect [:a :b :c] (filterv odd? [1 2 3 4]))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 Midje (for readablity)
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[midje.sweet :refer [fact facts =>]])
|
||||||
|
|
||||||
|
(fact "addition works"
|
||||||
|
(+ 2 2) => 4)
|
||||||
|
|
||||||
|
(facts "about strings"
|
||||||
|
(fact "upper-case works"
|
||||||
|
(clojure.string/upper-case "hello") => "HELLO"))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Development Workflow
|
||||||
|
|
||||||
|
### 4.1 REPL-driven Development
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Start REPL
|
||||||
|
user=> (require '[myproject.core :as core] :reload)
|
||||||
|
|
||||||
|
;; Edit code, reload
|
||||||
|
user=> (require '[myproject.core :as core] :reload :verbose)
|
||||||
|
|
||||||
|
;; Reload all changed namespaces
|
||||||
|
user=> (require '[clojure.tools.namespace.repl :as ns]
|
||||||
|
:refer [refresh refresh-all])
|
||||||
|
user=> (refresh)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Hot Loading Code
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; In REPL
|
||||||
|
(def server (atom nil))
|
||||||
|
|
||||||
|
(defn start-server []
|
||||||
|
(swap! server assoc :running true))
|
||||||
|
|
||||||
|
;; After editing core, just reload
|
||||||
|
(require '[myproject.core :as core] :reload)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Source Tracking
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Track which files changed
|
||||||
|
(require '[clojure.java.io :as io]
|
||||||
|
'[clojure.tools.namespace.track :as track])
|
||||||
|
|
||||||
|
(defn track-changes []
|
||||||
|
(let [tracker (atom (track/tracker))]
|
||||||
|
(fn []
|
||||||
|
(swap! tracker track/step)
|
||||||
|
(track/deps @tracker))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Rich Comment Blocks
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn process-data [input]
|
||||||
|
{:pre [(sequential? input)]}
|
||||||
|
(mapv inc input))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Development experiments
|
||||||
|
(process-data [1 2 3])
|
||||||
|
|
||||||
|
;; Test edge cases
|
||||||
|
(process-data [])
|
||||||
|
|
||||||
|
;; Interactive debugging
|
||||||
|
(def test-data (load-data))
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Code Quality Tools
|
||||||
|
|
||||||
|
### 5.1 eastwood (Linter)
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; deps.edn alias
|
||||||
|
:lint {:extra-deps {lancem对待/clojure-eastwood {:mvn/version "..."}}}
|
||||||
|
|
||||||
|
;; Run
|
||||||
|
clj -M:lint -m eastwood.lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 clj-kondo (Fast Linter)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install
|
||||||
|
brew install clj-kondo
|
||||||
|
|
||||||
|
# Run on project
|
||||||
|
clj-kondo --lint src/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Formatting with cljfmt
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; deps.edn
|
||||||
|
:format {:extra-deps {com.github.cljfmt/cljfmt {:mvn/version "..."}}}
|
||||||
|
|
||||||
|
;; Check
|
||||||
|
clj -M:format check src/
|
||||||
|
|
||||||
|
;; Fix
|
||||||
|
clj -M:format fix src/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Typed Clojure (Optional Type Checking)
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[clojure.core.typed :as t])
|
||||||
|
|
||||||
|
(t/ann my-function [t/Int -> t/Int])
|
||||||
|
(defn my-function [x]
|
||||||
|
(inc x))
|
||||||
|
|
||||||
|
;; Check types
|
||||||
|
(t/check-ns)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Building and Deployment
|
||||||
|
|
||||||
|
### 6.1 Building Uberjars
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; tools.build
|
||||||
|
(require '[clojure.tools.build.api :as b])
|
||||||
|
|
||||||
|
(def lib 'com.mycompany/myapp)
|
||||||
|
(def version "1.0.0")
|
||||||
|
(def target-dir "target")
|
||||||
|
|
||||||
|
(defn uberjar [opts]
|
||||||
|
(b/java-command
|
||||||
|
{:basis (:basis opts)
|
||||||
|
:main 'myproject.core
|
||||||
|
:jar-file (b/path target-dir "myapp.jar")
|
||||||
|
:uber-jar true}))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Native Image with GraalVM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compile Clojure to JVM bytecode first
|
||||||
|
clojure -M:build compile
|
||||||
|
|
||||||
|
# Build native image
|
||||||
|
native-image --initialize-at-build-time=clojure.lang.RT \
|
||||||
|
-jar myapp.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Docker Integration
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM clojure:openjdk-17-lein
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY deps.edn .
|
||||||
|
RUN clj -M -P
|
||||||
|
|
||||||
|
COPY src src
|
||||||
|
COPY test test
|
||||||
|
|
||||||
|
ENVlein uberjar
|
||||||
|
ENTRYPOINT ["java", "-jar", "target/myapp.jar"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 CI/CD Pipeline
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# GitHub Actions example
|
||||||
|
name: Clojure CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: DeLaGuardo/setup-clojure@10
|
||||||
|
with:
|
||||||
|
backend: 'cli'
|
||||||
|
version: '1.11.1'
|
||||||
|
- run: clj -M:test
|
||||||
|
- run: clj -M:lint
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Library Ecosystem
|
||||||
|
|
||||||
|
### 7.1 Web Development
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; HTTP Server - Ring (pure Clojure)
|
||||||
|
{:deps {ring/ring {:mvn/version "1.9.5"}}
|
||||||
|
|
||||||
|
;; Routes - Compojure
|
||||||
|
{:deps {compojure/compojure {:mvn/version "1.6.2"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Data Processing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Data manipulation
|
||||||
|
{:deps {zipkin/zikaron {:mvn/version "1.0.0"}}}
|
||||||
|
|
||||||
|
;; CSV handling
|
||||||
|
{:deps {org.clojure/data.csv {:mvn/version "1.0.1"}}
|
||||||
|
|
||||||
|
;; JSON (pure Clojure)
|
||||||
|
{:deps {org.clojure/data.json {:mvn/version "2.4.0"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Async Programming
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Core async is built-in
|
||||||
|
;; No additional dependencies for channels
|
||||||
|
|
||||||
|
;; For additional async patterns
|
||||||
|
{:deps {manifold/manifold {:mvn/version "0.4.0"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 Database Access
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Pure Clojure JDBC wrapper
|
||||||
|
{:deps {org.clojure/java.jdbc {:mvn/version "0.7.12"}}
|
||||||
|
|
||||||
|
;; SQL DSL
|
||||||
|
{:deps {sqlkorma/sqlkorma {:mvn/version "0.4.0"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.5 Testing Libraries
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
{:deps {expectations/expectations {:mvn/version "2.0.0"}}
|
||||||
|
{midje/midje {:mvn/version "1.9.10"}}
|
||||||
|
{org.clojure/test.check {:mvn/version "1.1.1"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.6 Finding Libraries
|
||||||
|
|
||||||
|
- Clojars.org - Community repository
|
||||||
|
-.Clojuredocs.org - Documentation with examples
|
||||||
|
- Awesome-clojure - Curated list
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Debugging Techniques
|
||||||
|
|
||||||
|
### 8.1 Print Debugging
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Simple prints
|
||||||
|
(println "Debug:" variable)
|
||||||
|
|
||||||
|
;; Withprympr
|
||||||
|
(require '[clojure.pprint :as pp])
|
||||||
|
(pp/pprint data-structure)
|
||||||
|
|
||||||
|
;; Tap for debugging
|
||||||
|
(tap> {:event :processing :data x})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Stack Traces
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Get full stack trace
|
||||||
|
(.printStackTrace *e)
|
||||||
|
|
||||||
|
;; Ex-data for spec errors
|
||||||
|
(try
|
||||||
|
(s/conform ::spec value)
|
||||||
|
(catch Exception e
|
||||||
|
(ex-data e)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 REPL Debugging
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Inspect values
|
||||||
|
user> (def data (s/gen ::my-spec))
|
||||||
|
user> data
|
||||||
|
|
||||||
|
;; Step through with trace
|
||||||
|
(trace (reduce + (range 10)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 Watching State
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Watch atoms
|
||||||
|
(add-watch my-atom :debug
|
||||||
|
(fn [k r o n]
|
||||||
|
(println "Changed:" o "->" n)))
|
||||||
|
|
||||||
|
;; Watch refs in transaction
|
||||||
|
(dosync
|
||||||
|
(trace (alter my-ref f)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.5 Breakpoints
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Using dbg macro (from tools.trace)
|
||||||
|
(require '[clojure.tools.trace :as t])
|
||||||
|
(t/dbg expression)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.6 Logging
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(require '[clojure.tools.logging :as log])
|
||||||
|
|
||||||
|
(log/info "Application started")
|
||||||
|
(log/debug "Processing" :item item)
|
||||||
|
(log/error e "Failed processing")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Index
|
||||||
|
|
||||||
|
### A
|
||||||
|
|
||||||
|
- `add-watch` - [8.4](#84-watching-state)
|
||||||
|
- `alias` - [1.2](#12-depsedn-reference)
|
||||||
|
|
||||||
|
### B
|
||||||
|
|
||||||
|
- `build` - [6.1](#61-building-uberjars)
|
||||||
|
|
||||||
|
### C
|
||||||
|
|
||||||
|
- `check-ns` - [5.4](#54-typed-clojure-optional-type-checking)
|
||||||
|
- `clojure.tools.namespace` - [4.1](#41-repl-driven-development)
|
||||||
|
- `comment` - [4.4](#44-rich-comment-blocks)
|
||||||
|
|
||||||
|
### D
|
||||||
|
|
||||||
|
- `dbg` - [8.5](#85-breakpoints)
|
||||||
|
- `deps.edn` - [2.1](#21-depsedn-reference)
|
||||||
|
|
||||||
|
### E
|
||||||
|
|
||||||
|
- `eastwood` - [5.1](#51-eastwood-linter)
|
||||||
|
|
||||||
|
### F
|
||||||
|
|
||||||
|
- `find-libs` - [7.6](#76-finding-libraries)
|
||||||
|
|
||||||
|
### G
|
||||||
|
|
||||||
|
- `gen` - [3.1](#31-testcheck-for-generative-testing)
|
||||||
|
|
||||||
|
### I
|
||||||
|
|
||||||
|
- `instrument` - [3.1](#31-testcheck-for-generative-testing)
|
||||||
|
|
||||||
|
### L
|
||||||
|
|
||||||
|
- `lein` - [6.3](#63-docker-integration)
|
||||||
|
- `load-file` - [4.2](#42-reloading-code)
|
||||||
|
|
||||||
|
### M
|
||||||
|
|
||||||
|
- `memoize` - [4.3](#43-source-tracking)
|
||||||
|
- `merge` - [8.2](#82-stack-traces)
|
||||||
|
|
||||||
|
### N
|
||||||
|
|
||||||
|
- `ns-publics` - [4.1](#41-repl-driven-development)
|
||||||
|
|
||||||
|
### P
|
||||||
|
|
||||||
|
- `pp/pprint` - [8.1](#81-print-debugging)
|
||||||
|
- `profile` - [8.3](#83-repl-debugging)
|
||||||
|
|
||||||
|
### Q
|
||||||
|
|
||||||
|
- `quick-check` - [3.1](#31-testcheck-for-generative-testing)
|
||||||
|
|
||||||
|
### R
|
||||||
|
|
||||||
|
- `reduce` - [8.3](#83-repl-debugging)
|
||||||
|
- `refresh` - [4.1](#41-repl-driven-development)
|
||||||
|
- `reload` - [4.1](#41-repl-driven-development)
|
||||||
|
- `run-tests` - [3.3](#33-test-namespaces)
|
||||||
|
|
||||||
|
### S
|
||||||
|
|
||||||
|
- `s/conform` - [8.2](#82-stack-traces)
|
||||||
|
- `s/gen` - [3.1](#31-testcheck-for-generative-testing)
|
||||||
|
- `shadow` - [6.2](#62-native-image-with-graalvm)
|
||||||
|
- `spec` - [8.2](#82-stack-traces)
|
||||||
|
|
||||||
|
### T
|
||||||
|
|
||||||
|
- `tap>` - [8.1](#81-print-debugging)
|
||||||
|
- `test` - [3.3](#33-test-namespaces)
|
||||||
|
- `trace` - [8.5](#85-breakpoints)
|
||||||
|
- `track` - [4.3](#43-source-tracking)
|
||||||
|
|
||||||
|
### U
|
||||||
|
|
||||||
|
- `uberjar` - [6.1](#61-building-uberjars)
|
||||||
|
- `use-fixtures` - [3.2](#32-fixtures-for-setupteardown)
|
||||||
|
|
||||||
|
### V
|
||||||
|
|
||||||
|
- `verify` - [3.1](#31-testcheck-for-generative-testing)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Pure Clojure: Project Structure and Tools*
|
||||||
@@ -0,0 +1,692 @@
|
|||||||
|
# Pure Clojure: Practical Recipes
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Common Patterns](#1-common-patterns)
|
||||||
|
2. [Data Transformation Recipes](#2-data-transformation-recipes)
|
||||||
|
3. [State Management Recipes](#3-state-management-recipes)
|
||||||
|
4. [Async Recipes](#4-async-recipes)
|
||||||
|
5. [Validation Recipes](#5-validation-recipes)
|
||||||
|
6. [API Design Patterns](#6-api-design-patterns)
|
||||||
|
7. [Testing Recipes](#7-testing-recipes)
|
||||||
|
8. [Performance Recipes](#8-performance-recipes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Common Patterns
|
||||||
|
|
||||||
|
### 1.1 Maybe/Option Pattern
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn safe-divide [a b]
|
||||||
|
(if (zero? b)
|
||||||
|
nil
|
||||||
|
(/ a b)))
|
||||||
|
|
||||||
|
(defn map-safe [f coll]
|
||||||
|
(sequence (comp (filter some?)
|
||||||
|
(map f))
|
||||||
|
coll))
|
||||||
|
|
||||||
|
(map-safe #(safe-divide 10 %) [2 0 4 0 5])
|
||||||
|
;; => (5 2 2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Either/Result Pattern
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn parse-int [s]
|
||||||
|
(try
|
||||||
|
{:success (Long/parseLong s)}
|
||||||
|
(catch NumberFormatException _
|
||||||
|
{:error "Invalid number"})))
|
||||||
|
|
||||||
|
(defn bind [result f]
|
||||||
|
(if (:error result)
|
||||||
|
result
|
||||||
|
(f (:success result))))
|
||||||
|
|
||||||
|
(-> (parse-int "42")
|
||||||
|
(bind #(* % 2))
|
||||||
|
(bind #(+ % 1)))
|
||||||
|
;; => {:success 85}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 State Machine Pattern
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defprotocol StateMachine
|
||||||
|
(transition [state event])
|
||||||
|
(current-state [state]))
|
||||||
|
|
||||||
|
(defrecord TrafficLight [state]
|
||||||
|
StateMachine
|
||||||
|
(transition [_ event]
|
||||||
|
(case [state event]
|
||||||
|
[:green :timeout] (->TrafficLight :yellow)
|
||||||
|
[:yellow :timeout] (->TrafficLight :red)
|
||||||
|
[:red :timeout] (->TrafficLight :green)
|
||||||
|
_))
|
||||||
|
(current-state [_] state))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 Builder Pattern
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn make-builder [defaults]
|
||||||
|
(let [state (atom defaults)]
|
||||||
|
(reify
|
||||||
|
Object
|
||||||
|
(toString [_] (str @state))
|
||||||
|
clojure.core.protocols/Coll
|
||||||
|
(coll [_] (seq @state))
|
||||||
|
clojure.lang.IFn
|
||||||
|
(invoke [_ k v]
|
||||||
|
(swap! state assoc k v)
|
||||||
|
this)
|
||||||
|
(invoke [_ m]
|
||||||
|
(swap! state merge m)
|
||||||
|
this))))
|
||||||
|
|
||||||
|
(def builder (make-builder {:debug false :timeout 5000}))
|
||||||
|
(-> builder
|
||||||
|
(assoc :host "localhost")
|
||||||
|
(merge {:port 8080})
|
||||||
|
str)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Data Transformation Recipes
|
||||||
|
|
||||||
|
### 2.1 Nested Data Access
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn get-in-safe [m keys default]
|
||||||
|
(try
|
||||||
|
(get-in m keys)
|
||||||
|
(catch NullPointerException _
|
||||||
|
default)))
|
||||||
|
|
||||||
|
;; With spec validation
|
||||||
|
(get-in-safe {:user {:address {:city "Sofia"}}}
|
||||||
|
[:user :address :city]
|
||||||
|
"Unknown")
|
||||||
|
|
||||||
|
;; Deep update
|
||||||
|
(defn update-in-safe [m keys f & args]
|
||||||
|
(if (get-in m keys)
|
||||||
|
(apply update-in m keys f args)
|
||||||
|
m))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Grouping and Aggregating
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Group by multiple keys
|
||||||
|
(defn group-by-multiple [ks coll]
|
||||||
|
(reduce
|
||||||
|
(fn [acc item]
|
||||||
|
(update-in acc (map item ks) conj item))
|
||||||
|
{}
|
||||||
|
coll))
|
||||||
|
|
||||||
|
(group-by-multiple [:department :role]
|
||||||
|
[{:name "Alice" :department "Eng" :role "Dev"}
|
||||||
|
{:name "Bob" :department "Eng" :role "Dev"}
|
||||||
|
{:name "Carol" :department "Sales" :role "Mgr"}])
|
||||||
|
|
||||||
|
;; Rolling aggregations
|
||||||
|
(defn rolling [f n coll]
|
||||||
|
(let [window (vec (take n coll))]
|
||||||
|
(lazy-seq
|
||||||
|
(cons (f window)
|
||||||
|
(rolling f n (rest coll))))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Pivot Tables
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn pivot-table [data row-key col-key value-fn]
|
||||||
|
(reduce
|
||||||
|
(fn [table row]
|
||||||
|
(let [r (row-key row)
|
||||||
|
c (col-key row)
|
||||||
|
v (value-fn row)]
|
||||||
|
(assoc-in table [r c] v)))
|
||||||
|
{}
|
||||||
|
data))
|
||||||
|
|
||||||
|
(pivot-table [{:month "Jan" :region "East" :sales 100}
|
||||||
|
{:month "Jan" :region "West" :sales 150}
|
||||||
|
{:month "Feb" :region "East" :sales 200}]
|
||||||
|
:month :region :sales)
|
||||||
|
;; => {"Jan" {"East" 100 "West" 150}
|
||||||
|
"Feb" {"East" 200}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Tree Operations
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Sum all numeric leaves
|
||||||
|
(defn tree-sum [tree]
|
||||||
|
(reduce + 0
|
||||||
|
(tree-seq sequential? seq tree)))
|
||||||
|
|
||||||
|
;; Map over tree
|
||||||
|
(defn tree-map [f tree]
|
||||||
|
(postwalk #(if (sequential? %) (mapv f %) %) tree))
|
||||||
|
|
||||||
|
;; Find in tree
|
||||||
|
(defn tree-find [pred tree]
|
||||||
|
(first (filter pred (tree-seq sequential? seq tree))))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. State Management Recipes
|
||||||
|
|
||||||
|
### 3.1 Service Pattern with Atoms
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defprotocol Service
|
||||||
|
(start [this])
|
||||||
|
(stop [this])
|
||||||
|
(process [this input]))
|
||||||
|
|
||||||
|
(defn make-service [config]
|
||||||
|
(let [state (atom {:config config
|
||||||
|
:running false
|
||||||
|
:cache {}})]
|
||||||
|
(reify
|
||||||
|
Service
|
||||||
|
(start [_]
|
||||||
|
(swap! state assoc :running true))
|
||||||
|
(stop [_]
|
||||||
|
(swap! state assoc :running false))
|
||||||
|
(process [_ input]
|
||||||
|
(when-not (:running @state)
|
||||||
|
(throw (ex-info "Service not running" {})))
|
||||||
|
(if-let [cached (get-in @state [:cache input])]
|
||||||
|
cached
|
||||||
|
(let [result (compute input)]
|
||||||
|
(swap! state assoc-in [:cache input] result)
|
||||||
|
result))))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Event Sourcing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn make-event-store []
|
||||||
|
(let [events (atom [])
|
||||||
|
snapshots (atom {})]
|
||||||
|
(reify
|
||||||
|
Object
|
||||||
|
(toString [_] (pr-str @events))
|
||||||
|
clojure.core.protocols/Coll
|
||||||
|
(coll [_] (seq @events))
|
||||||
|
clojure.lang.IFn
|
||||||
|
(invoke [_ event]
|
||||||
|
(let [new-state (apply-event @snapshots event)]
|
||||||
|
(swap! events conj event)
|
||||||
|
(when (seq? new-state)
|
||||||
|
(reset! snapshots new-state))))
|
||||||
|
(invoke [_ n]
|
||||||
|
(get @snapshots n)))))
|
||||||
|
|
||||||
|
(defn apply-event [state event]
|
||||||
|
(case (:type event)
|
||||||
|
:created (assoc state (:id event) (:data event))
|
||||||
|
:updated (update state (:id event) merge (:data event))
|
||||||
|
:deleted (dissoc state (:id event))
|
||||||
|
state))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Cooldown Mechanism
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn make-cooldown [timeout-ms]
|
||||||
|
(let [last-call (atom 0)]
|
||||||
|
(fn []
|
||||||
|
(let [now (System/currentTimeMillis)]
|
||||||
|
(when (> (- now @last-call) timeout-ms)
|
||||||
|
(reset! last-call now)
|
||||||
|
true)))))
|
||||||
|
|
||||||
|
(def rate-limiter (make-cooldown 1000))
|
||||||
|
|
||||||
|
;; Usage
|
||||||
|
(when (rate-limiter)
|
||||||
|
(do-something))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Circuit Breaker
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn make-circuit-breaker [failure-threshold reset-timeout]
|
||||||
|
(let [state (atom {:status :closed
|
||||||
|
:failures 0
|
||||||
|
:last-failure 0})]
|
||||||
|
(fn [f]
|
||||||
|
(let [current @state]
|
||||||
|
(case (:status current)
|
||||||
|
:open
|
||||||
|
(if (> (- (System/currentTimeMillis) (:last-failure current))
|
||||||
|
reset-timeout)
|
||||||
|
(do (swap! state assoc :status :half-open)
|
||||||
|
(try
|
||||||
|
(let [result (f)]
|
||||||
|
(swap! state assoc :status :closed :failures 0)
|
||||||
|
result)
|
||||||
|
(catch Exception e
|
||||||
|
(swap! state assoc :status :open :last-failure (System/currentTimeMillis))
|
||||||
|
(throw e))))
|
||||||
|
(throw (ex-info "Circuit open" {})))
|
||||||
|
:half-open
|
||||||
|
(try
|
||||||
|
(let [result (f)]
|
||||||
|
(swap! state assoc :status :closed :failures 0)
|
||||||
|
result)
|
||||||
|
(catch Exception e
|
||||||
|
(swap! state assoc :status :open :last-failure (System/currentTimeMillis))
|
||||||
|
(throw e)))
|
||||||
|
:closed
|
||||||
|
(try
|
||||||
|
(let [result (f)]
|
||||||
|
(swap! state assoc :failures 0)
|
||||||
|
result)
|
||||||
|
(catch Exception e
|
||||||
|
(let [failures (inc (:failures current))]
|
||||||
|
(swap! state assoc :failures failures
|
||||||
|
:last-failure (System/currentTimeMillis))
|
||||||
|
(when (>= failures failure-threshold)
|
||||||
|
(swap! state assoc :status :open))
|
||||||
|
(throw e)))))))))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Async Recipes
|
||||||
|
|
||||||
|
### 4.1 Channel Pipeline
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn channel-pipeline [in-f out-f & channels]
|
||||||
|
(doseq [ch channels]
|
||||||
|
(async/go
|
||||||
|
(loop []
|
||||||
|
(when-let [value (<! ch)]
|
||||||
|
(out-f value)
|
||||||
|
(recur))))))
|
||||||
|
|
||||||
|
;; Usage
|
||||||
|
(let [input-chan (async/chan 100)
|
||||||
|
process-chan (async/chan 100)
|
||||||
|
output-chan (async/chan 100)]
|
||||||
|
(async/pipeline 10 process-chan (map process-item) input-chan)
|
||||||
|
(async/pipeline 10 output-chan (map format-output) process-chan))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Multiplexing Channels
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn multiplex [in-chan & out-chans]
|
||||||
|
(async/go
|
||||||
|
(loop [value (<! in-chan)]
|
||||||
|
(when-not (nil? value)
|
||||||
|
(doseq [ch out-chans]
|
||||||
|
(>! ch value))
|
||||||
|
(recur (<! in-chan))))))
|
||||||
|
|
||||||
|
;; Usage
|
||||||
|
(let [input (async/chan)
|
||||||
|
out1 (async/chan)
|
||||||
|
out2 (async/chan)]
|
||||||
|
(multiplex input out1 out2)
|
||||||
|
;; Now input is broadcast to both outputs
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Timeout Patterns
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Timeout on operation
|
||||||
|
(async/go
|
||||||
|
(let [result (async/alts!! [work-chan
|
||||||
|
(async/timeout 5000)])]
|
||||||
|
(if (= result :timed-out)
|
||||||
|
{:status :timeout}
|
||||||
|
{:status :success :value (first result)})))
|
||||||
|
|
||||||
|
;; Retry with backoff
|
||||||
|
(defn with-retry [f max-attempts delay-ms]
|
||||||
|
(async/go
|
||||||
|
(loop [attempts 0]
|
||||||
|
(let [result (async/<! (async/timeout delay-ms))]
|
||||||
|
(if (= :failure result)
|
||||||
|
(if (< attempts max-attempts)
|
||||||
|
(recur (inc attempts))
|
||||||
|
{:status :failed})
|
||||||
|
{:status :success :value result})))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Windowing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn windowed [in size overlap]
|
||||||
|
(let [out (async/chan)]
|
||||||
|
(async/go
|
||||||
|
(loop [window (vec (take size (async/<! in)))]
|
||||||
|
(when (seq window)
|
||||||
|
(>! out window)
|
||||||
|
(let [next-items (vec (take overlap (rest window)))
|
||||||
|
remaining (- size overlap)
|
||||||
|
new-items (vec (take remaining (async/<! in)))]
|
||||||
|
(recur (into next-items new-items))))))
|
||||||
|
out))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Validation Recipes
|
||||||
|
|
||||||
|
### 5.1 Multi-stage Validation
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn validate-stages [data & validators]
|
||||||
|
(reduce
|
||||||
|
(fn [result validator]
|
||||||
|
(let [errors (validator result)]
|
||||||
|
(if (seq errors)
|
||||||
|
(reduced {:status :error :errors errors})
|
||||||
|
result)))
|
||||||
|
{:status :ok :data data}
|
||||||
|
validators))
|
||||||
|
|
||||||
|
(defn non-blank [field]
|
||||||
|
(fn [result]
|
||||||
|
(when (clojure.string/blank? (get-in result [:data field]))
|
||||||
|
[field "cannot be blank"])))
|
||||||
|
|
||||||
|
(defn max-length [field length]
|
||||||
|
(fn [result]
|
||||||
|
(when (> (count (get-in result [:data field])) length)
|
||||||
|
[field (str "must be at most" length "characters")])))
|
||||||
|
|
||||||
|
(validate-stages
|
||||||
|
{:name ""}
|
||||||
|
(non-blank :name)
|
||||||
|
(max-length :name 50))
|
||||||
|
;; => {:status :error :errors [:name "cannot be blank"]}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Schema Validation
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def email-regex #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
|
||||||
|
|
||||||
|
(defn validate-email [email]
|
||||||
|
(when-not (re-find email-regex email)
|
||||||
|
"Invalid email format"))
|
||||||
|
|
||||||
|
(defn validate-user [user]
|
||||||
|
(reduce-kv
|
||||||
|
(fn [errors field validate]
|
||||||
|
(if-let [error (validate (get user field))]
|
||||||
|
(conj errors [field error])
|
||||||
|
errors))
|
||||||
|
[]
|
||||||
|
{:name [#(when (clojure.string/blank? %) "Name required")]
|
||||||
|
:email [validate-email
|
||||||
|
#(when (> (count %) 100) "Email too long")]
|
||||||
|
:age [#(when (or (nil? %) (neg? %)) "Age must be positive")]}))
|
||||||
|
|
||||||
|
(validate-user {:name "John" :email "john@example.com" :age 30})
|
||||||
|
;; => []
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Contract Testing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defmacro defcontract [name input-spec output-spec & body]
|
||||||
|
`(defn ~name [& args#]
|
||||||
|
(let [input# (first args#)
|
||||||
|
output# (apply ~(into `fn input-spec `@body) args#)]
|
||||||
|
(when-not (~output-spec output#)
|
||||||
|
(throw (ex-info "Contract violation"
|
||||||
|
{:function '~name
|
||||||
|
:input input#
|
||||||
|
:output output#})))
|
||||||
|
output#)))
|
||||||
|
|
||||||
|
;; Usage
|
||||||
|
(defcontract add-positive
|
||||||
|
[a number? b number?] ;; Input spec
|
||||||
|
number? ;; Output spec
|
||||||
|
[a b]
|
||||||
|
(+ a b))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. API Design Patterns
|
||||||
|
|
||||||
|
### 6.1 Ring Handlers (Pure Functions)
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn wrap-logging [handler]
|
||||||
|
(fn [request]
|
||||||
|
(println "Request:" (:uri request))
|
||||||
|
(let [response (handler request)]
|
||||||
|
(println "Response:" (:status response))
|
||||||
|
response)))
|
||||||
|
|
||||||
|
(defn wrap-cors [handler]
|
||||||
|
(fn [request]
|
||||||
|
(let [response (handler request)]
|
||||||
|
(assoc response :headers
|
||||||
|
(merge (:headers response {})
|
||||||
|
{"Access-Control-Allow-Origin" "*"})))))
|
||||||
|
|
||||||
|
;; Pure handler
|
||||||
|
(defn handle-get-user [request]
|
||||||
|
{:status 200
|
||||||
|
:headers {"Content-Type" "application/json"}
|
||||||
|
:body (pr-str {:name "John" :email "john@example.com"})})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Middleware Stack
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn apply-middleware [handler middlewares]
|
||||||
|
(reduce
|
||||||
|
(fn [h middleware]
|
||||||
|
(middleware h))
|
||||||
|
handler
|
||||||
|
middlewares))
|
||||||
|
|
||||||
|
(def app
|
||||||
|
(-> handler-get-user
|
||||||
|
(apply-middleware [wrap-cors
|
||||||
|
wrap-logging
|
||||||
|
wrap-auth])))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Route Definitions
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def routes
|
||||||
|
[[:get "/users" list-users]
|
||||||
|
[:get "/users/:id" get-user]
|
||||||
|
[:post "/users" create-user]
|
||||||
|
[:put "/users/:id" update-user]
|
||||||
|
[:delete "/users/:id" delete-user]])
|
||||||
|
|
||||||
|
(defn match-route [method path]
|
||||||
|
(some
|
||||||
|
(fn [[m p handler]]
|
||||||
|
(when (and (= method m)
|
||||||
|
(re-matches (路由->regex p) path))
|
||||||
|
{:handler handler
|
||||||
|
:params (extract-params p path)}))
|
||||||
|
routes))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 Error Handling Middleware
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn wrap-exception [handler]
|
||||||
|
(fn [request]
|
||||||
|
(try
|
||||||
|
(handler request)
|
||||||
|
(catch Exception e
|
||||||
|
{:status 500
|
||||||
|
:headers {"Content-Type" "application/json"}
|
||||||
|
:body (pr-str {:error (ex-message e)
|
||||||
|
:data (ex-data e)})}))))
|
||||||
|
|
||||||
|
(defn wrap-not-found [handler]
|
||||||
|
(fn [request]
|
||||||
|
(let [response (handler request)]
|
||||||
|
(if (= (:status response) 404)
|
||||||
|
{:status 404
|
||||||
|
:body "Not Found"}
|
||||||
|
response))))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Testing Recipes
|
||||||
|
|
||||||
|
### 7.1 Property-Based Testing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defcommutative +
|
||||||
|
[a integer? b integer?]
|
||||||
|
(= (+ a b) (+ b a)))
|
||||||
|
|
||||||
|
(defassociative +
|
||||||
|
[a integer? b integer? c integer?]
|
||||||
|
(= (+ (+ a b) c) (+ a (+ b c))))
|
||||||
|
|
||||||
|
;; Idempotent operations
|
||||||
|
(defidempotent conj
|
||||||
|
[coll vector? item any?]
|
||||||
|
(= (conj (conj coll item) item)
|
||||||
|
(conj coll item)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Test Fixtures with Random Data
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn with-sample-data [f]
|
||||||
|
(let [samples (gen/sample (s/gen ::user) 10)]
|
||||||
|
(doseq [sample samples]
|
||||||
|
(f sample))))
|
||||||
|
|
||||||
|
(t/use-fixtures :each with-sample-data)
|
||||||
|
|
||||||
|
(t/deftest user-validation-test
|
||||||
|
[sample]
|
||||||
|
(t/is (nil? (validate-user sample))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Mutation Testing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Simple mutation testing
|
||||||
|
(defn mutate-and-test [original-fn test-fn mutation]
|
||||||
|
(let [mutated (mutation original-fn)]
|
||||||
|
(try
|
||||||
|
(test-fn mutated)
|
||||||
|
false ;; Test passed on mutation = bad
|
||||||
|
(catch AssertionError _
|
||||||
|
true)))) ;; Test caught mutation = good
|
||||||
|
|
||||||
|
;; Random mutation generator
|
||||||
|
(defn random-mutation [f]
|
||||||
|
(let [mutations [(fn [x] (inc x))
|
||||||
|
(fn [x] (dec x))
|
||||||
|
(fn [x] (* x 2))]]
|
||||||
|
(some #(% f) mutations)))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Performance Recipes
|
||||||
|
|
||||||
|
### 8.1 Batched Processing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn batch-process [items batch-size f]
|
||||||
|
(into []
|
||||||
|
(mapcat f)
|
||||||
|
(partition-all batch-size items)))
|
||||||
|
|
||||||
|
;; Usage
|
||||||
|
(batch-process (range 10000) 100
|
||||||
|
(fn [batch]
|
||||||
|
(mapv expensive-operation batch)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Caching with TTL
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn make-ttl-cache [ttl-ms]
|
||||||
|
(let [cache (atom {})
|
||||||
|
cleanup (fn []
|
||||||
|
(let [now (System/currentTimeMillis)]
|
||||||
|
(swap! cache
|
||||||
|
(fn [m]
|
||||||
|
(into {}
|
||||||
|
(filter #(< (- now (val %)) ttl-ms))
|
||||||
|
m)))))]
|
||||||
|
(fn [f]
|
||||||
|
(fn [k]
|
||||||
|
(cleanup)
|
||||||
|
(if-let [entry (get @cache k)]
|
||||||
|
(val entry)
|
||||||
|
(let [result (f k)]
|
||||||
|
(swap! cache assoc k [(System/currentTimeMillis) result])
|
||||||
|
result))))))
|
||||||
|
|
||||||
|
(def cached-heavy-operation (make-ttl-cache 60000) heavy-operation)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 Lazy File Processing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn lazy-file-lines [filepath]
|
||||||
|
(line-seq (clojure.java.io/reader filepath)))
|
||||||
|
|
||||||
|
(defn lazy-csv-rows [filepath]
|
||||||
|
(map #(clojure.string/split % #",")
|
||||||
|
(lazy-file-lines filepath)))
|
||||||
|
|
||||||
|
;; Process huge files line by line
|
||||||
|
(into []
|
||||||
|
(comp
|
||||||
|
(drop 1) ;; Skip header
|
||||||
|
(map #(update % 2 parse-long)) ;; Transform column
|
||||||
|
(filter #(= "active" (% 3))))
|
||||||
|
(take 1000 (lazy-csv-rows "large-file.csv")))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 Parallel Collection Processing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn parallel-map [f coll n]
|
||||||
|
(let [parts (partition-all (/ (count coll) n) coll)
|
||||||
|
results (pmap #(doall (map f %)) parts)]
|
||||||
|
(apply concat results)))
|
||||||
|
|
||||||
|
;; With reducers for better performance
|
||||||
|
(require '[clojure.core.reducers :as r])
|
||||||
|
|
||||||
|
(defn parallel-reduce [f init coll]
|
||||||
|
(r/fold (/ (count coll) 4) f coll))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Pure Clojure: Practical Recipes*
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
# Clojure/Nim: Native Clojure
|
||||||
|
|
||||||
|
> The same language you love. Compiled to native binaries. No JVM required.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What is Clojure/Nim?
|
||||||
|
|
||||||
|
**Clojure/Nim** is a complete Clojure dialect that compiles to native machine code via the Nim compiler. It is not an interpreter — it is a real compiler with a full optimization pipeline:
|
||||||
|
|
||||||
|
```
|
||||||
|
Your .clj file
|
||||||
|
↓
|
||||||
|
Reader (EDN parser)
|
||||||
|
↓
|
||||||
|
Macro expansion (defmacro, syntax-quote, ->, ->>)
|
||||||
|
↓
|
||||||
|
Emitter (Clojure AST → Nim source)
|
||||||
|
↓
|
||||||
|
Nim Compiler → C code
|
||||||
|
↓
|
||||||
|
C Compiler → Native binary
|
||||||
|
```
|
||||||
|
|
||||||
|
The result is a single executable file, often under **1 MB**, that starts instantly with **no JVM warmup**.
|
||||||
|
|
||||||
|
### Why Native?
|
||||||
|
|
||||||
|
| JVM Clojure | Clojure/Nim |
|
||||||
|
|-------------|-------------|
|
||||||
|
| Needs Java runtime installed | Self-contained binary |
|
||||||
|
| ~2-5 second startup time | Instant startup |
|
||||||
|
| ~100-300 MB memory footprint | ~1-10 MB |
|
||||||
|
| JIT compilation pauses | Ahead-of-time, predictable |
|
||||||
|
| Great for long-running servers | Great for CLI tools, embedded, WASM |
|
||||||
|
|
||||||
|
This is not a toy. It is a production compiler with **276+ tests**, **persistent data structures (HAMT)**, **core.async channels**, and an **AI-assisted workflow**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitlab.com/balvatar/lisp-nim.git
|
||||||
|
cd lisp-nim
|
||||||
|
make build
|
||||||
|
make check # run all tests + examples
|
||||||
|
```
|
||||||
|
|
||||||
|
**Requirements:** Nim ≥ 2.0, GCC or Clang, make.
|
||||||
|
|
||||||
|
That is it. No Java. No Leiningen. No deps downloads that take ten minutes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Your First Program
|
||||||
|
|
||||||
|
Create `hello.clj`:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(println "Hello from native Clojure!")
|
||||||
|
(println (+ 1 2 3 4 5))
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./cljnim run hello.clj
|
||||||
|
Hello from native Clojure!
|
||||||
|
15
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice: the program was parsed, macro-expanded, emitted as Nim, compiled to C, compiled to machine code, and executed — all in under a second.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The REPL: Two Modes
|
||||||
|
|
||||||
|
### Human REPL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./cljnim repl
|
||||||
|
user> (defn square [x] (* x x))
|
||||||
|
user> (square 7)
|
||||||
|
=> 49
|
||||||
|
user> (atom 42)
|
||||||
|
=> (atom 42)
|
||||||
|
user> :ai function for fibonacci
|
||||||
|
🤖 Thinking...
|
||||||
|
💡 AI Suggestion:
|
||||||
|
(defn fib [n]
|
||||||
|
(loop [a 0 b 1 i 0]
|
||||||
|
(if (= i n) a (recur b (+ a b) (inc i)))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON REPL (for AI Agents)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./cljnim repl --json
|
||||||
|
{"status":"ready","ns":"user","mode":"json"}
|
||||||
|
|
||||||
|
> {"op":"eval","form":"(+ 1 2 3)"}
|
||||||
|
{"status":"ok","result":{"printed":"6"},"meta":{"ms":12}}
|
||||||
|
|
||||||
|
> {"tool":"cljnim/eval","args":{"form":"(defn greet [name] (str \"Hello \" name))"}}
|
||||||
|
{"status":"ok","result":{"type":"var","name":"greet"},...}
|
||||||
|
```
|
||||||
|
|
||||||
|
The JSON REPL is designed for programmatic interaction. Every operation has structured input and output. AI agents can discover capabilities, evaluate code, inspect definitions, and batch-process forms without parsing human-readable text.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AI-Powered Development
|
||||||
|
|
||||||
|
Clojure/Nim is the first Clojure implementation built with AI assistance as a first-class feature.
|
||||||
|
|
||||||
|
### Error Explanation
|
||||||
|
|
||||||
|
When compilation fails, the compiler asks an AI for help:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./cljnim run broken.clj
|
||||||
|
Compilation failed
|
||||||
|
Error: identifier expected, but got 'keyword var'
|
||||||
|
|
||||||
|
💡 AI Suggestion:
|
||||||
|
The error occurs because `var` is a reserved keyword in Nim.
|
||||||
|
Fix: Rename the function to `my-var`.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Generation
|
||||||
|
|
||||||
|
Generate idiomatic Clojure from a description:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./cljnim ai "function that filters even numbers"
|
||||||
|
(defn filter-even [coll]
|
||||||
|
(loop [remaining coll result []]
|
||||||
|
(if (empty? remaining)
|
||||||
|
result
|
||||||
|
(let [x (first remaining)]
|
||||||
|
(recur (rest remaining)
|
||||||
|
(if (even? x) (conj result x) result))))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export DEEPSEEK_API_KEY="sk-..."
|
||||||
|
# or OPENAI_API_KEY, or MIMO_API_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `loop` / `recur`: Real TCO
|
||||||
|
|
||||||
|
Unlike JVM Clojure (which uses `recur` to avoid stack overflow but still runs on a stack-based VM), Clojure/Nim compiles `loop`/`recur` to a native `while` loop:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn factorial [n]
|
||||||
|
(loop [acc 1 i n]
|
||||||
|
(if (= i 0)
|
||||||
|
acc
|
||||||
|
(recur (* acc i) (dec i)))))
|
||||||
|
```
|
||||||
|
|
||||||
|
This generates efficient C code with no function calls. It is **genuinely O(1) stack space**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cross-Compilation Targets
|
||||||
|
|
||||||
|
Clojure/Nim can target multiple platforms from the same source:
|
||||||
|
|
||||||
|
### Native Binary (default)
|
||||||
|
```bash
|
||||||
|
./cljnim run program.clj
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript
|
||||||
|
```bash
|
||||||
|
./cljnim compile program.clj program.nim
|
||||||
|
nim js -o:program.js program.nim
|
||||||
|
node program.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### C Shared Library
|
||||||
|
```bash
|
||||||
|
./cljnim compile-lib program.clj program.nim
|
||||||
|
nim c --app:lib -o:libprogram.so program.nim
|
||||||
|
```
|
||||||
|
|
||||||
|
### WASM (experimental)
|
||||||
|
```bash
|
||||||
|
# Via Zig or Emscripten
|
||||||
|
nim c -d:release --cpu:wasm32 --os:linux program.nim
|
||||||
|
```
|
||||||
|
|
||||||
|
This is something JVM Clojure simply cannot do.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Persistent Data Structures
|
||||||
|
|
||||||
|
Clojure/Nim implements real **Hash Array Mapped Trie (HAMT)** vectors and maps:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def v (vector 1 2 3))
|
||||||
|
(def v2 (conj v 4))
|
||||||
|
;; v => [1 2 3]
|
||||||
|
;; v2 => [1 2 3 4]
|
||||||
|
;; Structural sharing: O(log₃₂ n) updates, not O(n) copies
|
||||||
|
```
|
||||||
|
|
||||||
|
The runtime uses the same algorithms as Clojure/JVM (32-way branching, path copying, tail optimization), but compiled to bare metal.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Concurrency
|
||||||
|
|
||||||
|
### Atoms
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def counter (atom 0))
|
||||||
|
(swap! counter inc) ;; => 1
|
||||||
|
(reset! counter 100) ;; => 100
|
||||||
|
(deref counter) ;; => 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### Agents
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def state (agent 0))
|
||||||
|
(send state + 10)
|
||||||
|
(await state)
|
||||||
|
(deref state) ;; => 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Channels (core.async)
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(def ch (chan 10))
|
||||||
|
(put! ch 42)
|
||||||
|
(take! ch) ;; => 42
|
||||||
|
(close! ch)
|
||||||
|
```
|
||||||
|
|
||||||
|
All concurrency primitives work in both the compiled runtime and the in-memory interpreter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Macros That Work
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defmacro unless [condition body]
|
||||||
|
`(if (not ~condition)
|
||||||
|
~body))
|
||||||
|
|
||||||
|
(unless false
|
||||||
|
(println "This prints!"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Threading macros, `when-let`, `cond`, `doto`, `some->` — all implemented as real Clojure macros, expanded at compile time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## When to Use Clojure/Nim
|
||||||
|
|
||||||
|
| Use Case | Clojure/JVM | Clojure/Nim |
|
||||||
|
|----------|-------------|-------------|
|
||||||
|
| Large web services | ✅ | ⚠️ (early stage) |
|
||||||
|
| CLI tools | ⚠️ (slow startup) | ✅ (instant) |
|
||||||
|
| Embedded systems | ❌ | ✅ |
|
||||||
|
| WASM / browser | ClojureScript | ✅ (native WASM) |
|
||||||
|
| Shared libraries | ❌ | ✅ |
|
||||||
|
| AI agent scripting | ❌ | ✅ (JSON REPL) |
|
||||||
|
| Learning Clojure | ✅ | ✅ (no JVM needed) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Further Reading
|
||||||
|
|
||||||
|
- [Getting Started](01-fundamentals.md) — Core Clojure concepts (applies to all dialects)
|
||||||
|
- [Architecture](../../docs/en/02-architecture.md) — How the compiler works internally
|
||||||
|
- [AI Integration](../../docs/en/03-ai-integration.md) — Deep API details for AI features
|
||||||
|
- [API Reference](../../docs/en/04-api-reference.md) — JSON REPL protocol specification
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Clojure/Nim is proof that you do not need a virtual machine to write elegant, functional, immutable code. You just need a good compiler.*
|
||||||
@@ -0,0 +1,375 @@
|
|||||||
|
# Subject Index - English
|
||||||
|
|
||||||
|
## A
|
||||||
|
|
||||||
|
- **Agents** - [11.3](en/01-fundamentals.md#113-agents)
|
||||||
|
- **Aliasing namespaces** - [9.3](en/01-fundamentals.md#93-referring-and-importing)
|
||||||
|
- **Allocation (memory)** - [8.2](en/02-advanced.md#82-transient-data-structures)
|
||||||
|
- **Anonymous functions** - [5.2](en/01-fundamentals.md#52-anonymous-functions)
|
||||||
|
- **Antiquated functions** - [17.3](en/01-fundamentals.md#173-naming-conventions)
|
||||||
|
- **Apply** - [7.3.3](en/01-fundamentals.md#733-reduce)
|
||||||
|
- **Arities** - [5.1.2](en/01-fundamentals.md#512-multiple-arities)
|
||||||
|
- **Asynchronous programming** - [16](en/01-fundamentals.md#16-coreasync), [4](en/04-recipes.md#4-async-recipes)
|
||||||
|
- **Atom** - [11.1](en/01-fundamentals.md#111-atoms)
|
||||||
|
|
||||||
|
## B
|
||||||
|
|
||||||
|
- **Batch processing** - [8.5](en/02-advanced.md#85-batch-processing), [8.1](en/04-recipes.md#81-batch-processing)
|
||||||
|
- **Behavior dispatch** - [13](en/01-fundamentals.md#13-multimethods)
|
||||||
|
- **Bencharking** - [8.7](en/02-advanced.md#87-bencharking)
|
||||||
|
- **Binding** - [11.4](en/01-fundamentals.md#114-vars)
|
||||||
|
- **Booleans** - [4.4](en/01-fundamentals.md#44-booleans)
|
||||||
|
|
||||||
|
## C
|
||||||
|
|
||||||
|
- **Caching** - [1.5](en/01-fundamentals.md#15-memoization), [8.6](en/02-advanced.md#86-preload-and-cache), [8.2](en/04-recipes.md#82-caching-with-ttl)
|
||||||
|
- **Case** - [6.1.5](en/01-fundamentals.md#615-case)
|
||||||
|
- **Channels** - [16.1](en/01-fundamentals.md#161-channels), [5](en/05-clojure-nim.md#concurrency)
|
||||||
|
- **Clojure/Nim** - [5](en/05-clojure-nim.md) — Native compilation, AI integration, JSON REPL
|
||||||
|
- **Compilation (native)** - [5](en/05-clojure-nim.md#what-is-clojure-nim)
|
||||||
|
- **Cross-compilation** - [5](en/05-clojure-nim.md#cross-compilation-targets) — JS, WASM, shared libraries
|
||||||
|
- **Characters** - [4.3](en/01-fundamentals.md#43-characters)
|
||||||
|
- **Chunked sequences** - [2.2](en/02-advanced.md#22-chunked-sequences)
|
||||||
|
- **Circuit breaker** - [3.4](en/04-recipes.md#34-circuit-breaker)
|
||||||
|
- **Closures** - [5.4](en/01-fundamentals.md#54-closures)
|
||||||
|
- **Coll** - [5.3](en/02-advanced.md#53-extending-collections)
|
||||||
|
- **Comment** - [3.5](en/01-fundamentals.md#35-comments)
|
||||||
|
- **Commitment** - [3.3](en/04-recipes.md#33-cooldown-mechanism)
|
||||||
|
- **Compilation** - [6.1](en/03-tooling.md#61-building-uberjars)
|
||||||
|
- **Composing functions** - [5.3](en/01-fundamentals.md#53-higher-order-functions)
|
||||||
|
- **concat** - [4.7](en/01-fundamentals.md#47-lists)
|
||||||
|
- **Concurrency** - [11](en/01-fundamentals.md#11-concurrency)
|
||||||
|
- **cond** - [6.1.3](en/01-fundamentals.md#613-cond)
|
||||||
|
- **condp** - [6.1.4](en/01-fundamentals.md#614-condp)
|
||||||
|
- **Configuration** - [1.3](en/01-fundamentals.md#13-keyword-arguments)
|
||||||
|
- **Cons** - [4.7](en/01-fundamentals.md#47-lists)
|
||||||
|
- **Contracts** - [5.3](en/04-recipes.md#53-contract-testing)
|
||||||
|
- **Core.async** - [16](en/01-fundamentals.md#16-coreasync)
|
||||||
|
- **Currying** - [5.3](en/01-fundamentals.md#53-higher-order-functions)
|
||||||
|
|
||||||
|
## D
|
||||||
|
|
||||||
|
- **Data structures** - [4](en/01-fundamentals.md#4-data-structures)
|
||||||
|
- **dead** - [11.7](en/01-fundamentals.md#117-stm-guidelines)
|
||||||
|
- **deb** - [8.5](en/03-tooling.md#85-breakpoints)
|
||||||
|
- **Debugging** - [8](en/03-tooling.md#8-debugging-techniques)
|
||||||
|
- **def** - [3.3.1](en/01-fundamentals.md#331-def)
|
||||||
|
- **defmacro** - [10.1](en/01-fundamentals.md#101-what-are-macros)
|
||||||
|
- **defmethod** - [13.1](en/01-fundamentals.md#131-defining-multimethods)
|
||||||
|
- **defmulti** - [13.1](en/01-fundamentals.md#131-defining-multimethods)
|
||||||
|
- **defn** - [5.1.1](en/01-fundamentals.md#511-basic-syntax)
|
||||||
|
- **defprotocol** - [12.1](en/01-fundamentals.md#121-protocols)
|
||||||
|
- **defrecord** - [12.2](en/01-fundamentals.md#122-records)
|
||||||
|
- **delay** - [11.6](en/01-fundamentals.md#116-promises-and-delivered)
|
||||||
|
- **Dependency management** - [2](en/03-tooling.md#2-depsedn-and-cli-tools)
|
||||||
|
- **Destructuring** - [8](en/01-fundamentals.md#8-destructuring)
|
||||||
|
- **disj** - [4.10](en/01-fundamentals.md#410-sets)
|
||||||
|
- **dissoc** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
|
- **Docker** - [6.3](en/03-tooling.md#63-docker-integration)
|
||||||
|
- **doseq** - [6.2.4](en/01-fundamentals.md#624-doseq-side-effects)
|
||||||
|
- **dosync** - [11.2](en/01-fundamentals.md#112-refs)
|
||||||
|
- **dotimes** - [6.2.3](en/01-fundamentals.md#623-for-list-comprehension)
|
||||||
|
- **drop** - [7.3.6](en/01-fundamentals.md#736-take--drop)
|
||||||
|
- **drop-while** - [7.3.6](en/01-fundamentals.md#736-take--drop)
|
||||||
|
|
||||||
|
## E
|
||||||
|
|
||||||
|
- **eastwood** - [5.1](en/03-tooling.md#51-eastwood-linter)
|
||||||
|
- **Either pattern** - [1.2](en/04-recipes.md#12-eitherresult-pattern)
|
||||||
|
- **emit** - [6.1](en/03-tooling.md#61-building-uberjars)
|
||||||
|
- **empty** - [4.12](en/01-fundamentals.md#412-collections-library)
|
||||||
|
- **Enumerations** - [4.5](en/01-fundamentals.md#45-keywords)
|
||||||
|
- **Error handling** - [6.3](en/01-fundamentals.md#63-exception-handling), [6.4](en/04-recipes.md#64-error-handling-middleware)
|
||||||
|
- **Evaluation rules** - [3.4](en/01-fundamentals.md#34-evaluation-rules)
|
||||||
|
- **Event sourcing** - [3.2](en/04-recipes.md#32-event-sourcing)
|
||||||
|
- **Exception handling** - [6.3](en/01-fundamentals.md#63-exception-handling)
|
||||||
|
- **expand** - [10.5](en/01-fundamentals.md#105-macro-expansion)
|
||||||
|
- **Extending types** - [12.3](en/01-fundamentals.md#123-extending-existing-types)
|
||||||
|
|
||||||
|
## F
|
||||||
|
|
||||||
|
- **Fibonacci** - [7.2](en/01-fundamentals.md#72-lazy-sequences)
|
||||||
|
- **filter** - [7.3.2](en/01-fundamentals.md#732-filter--remove)
|
||||||
|
- **find-doc** - [15.1](en/01-fundamentals.md#151-repl-commands)
|
||||||
|
- **First** - [4.7](en/01-fundamentals.md#47-lists)
|
||||||
|
- **Flatten** - [7.3.7](en/01-fundamentals.md#737-interpose--interleave)
|
||||||
|
- **flip** - [11.2](en/01-fundamentals.md#112-refs)
|
||||||
|
- **Float** - [4.1.2](en/01-fundamentals.md#412-floating-point)
|
||||||
|
- **fold** - [7.3.3](en/01-fundamentals.md#733-reduce), [6.2](en/02-advanced.md#62-using-reducers)
|
||||||
|
- **for** - [6.2.3](en/01-fundamentals.md#623-for-list-comprehension)
|
||||||
|
- **force** - [11.6](en/01-fundamentals.md#116-promises-and-delivered)
|
||||||
|
- **Formatting** - [3.6](en/01-fundamentals.md#36-whitespace-and-formatting)
|
||||||
|
- **Function composition** - [5.3](en/01-fundamentals.md#53-higher-order-functions)
|
||||||
|
- **Functional programming** - [1.2.2](en/01-fundamentals.md#122-functional-programming)
|
||||||
|
- **Functions** - [5](en/01-fundamentals.md#5-functions)
|
||||||
|
- **Futures** - [11.5](en/01-fundamentals.md#115-futures)
|
||||||
|
|
||||||
|
## G
|
||||||
|
|
||||||
|
- **gen** - [4.5](en/02-advanced.md#45-generative-testing)
|
||||||
|
- **Generative testing** - [14.4](en/01-fundamentals.md#144-generative-testing)
|
||||||
|
- **get** - [4.8](en/01-fundamentals.md#48-vectors)
|
||||||
|
- **get-in** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
|
- **Glossary** - [Appendix B](en/01-fundamentals.md#appendix-b-glossary)
|
||||||
|
- **group-by** - [7.3.7](en/01-fundamentals.md#737-interpose--interleave)
|
||||||
|
|
||||||
|
## H
|
||||||
|
|
||||||
|
- **hash-map** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
|
- **hash-set** - [4.10](en/01-fundamentals.md#410-sets)
|
||||||
|
- **Hierarchies** - [13.3](en/01-fundamentals.md#133-hierarchies)
|
||||||
|
- **Higher-order functions** - [5.3](en/01-fundamentals.md#53-higher-order-functions)
|
||||||
|
- **hot** - [4.2](en/03-tooling.md#42-reloading-code)
|
||||||
|
|
||||||
|
## I
|
||||||
|
|
||||||
|
- **Identities** - [11](en/01-fundamentals.md#11-concurrency)
|
||||||
|
- **if** - [3.3.3](en/01-fundamentals.md#333-if)
|
||||||
|
- **if-let** - [6.1.2](en/01-fundamentals.md#612-when--when-not)
|
||||||
|
- **if-not** - [6.1.1](en/01-fundamentals.md#611-if--if-not)
|
||||||
|
- **Immutability** - [1.2.1](en/01-fundamentals.md#121-immutability-by-default), [4](en/01-fundamentals.md#4-data-structures)
|
||||||
|
- **import** - [9.3](en/01-fundamentals.md#93-referring-and-importing)
|
||||||
|
- **in-ns** - [9.1](en/01-fundamentals.md#91-creating-and-switching-namespaces)
|
||||||
|
- **inc** - [4.2](en/01-fundamentals.md#42-strings)
|
||||||
|
- **indexed** - [7.3.7](en/01-fundamentals.md#737-interpose--interleave)
|
||||||
|
- **Infinite sequences** - [2.5](en/02-advanced.md#25-infinite-sequences)
|
||||||
|
- **Injection** - [7.3.8](en/01-fundamentals.md#738-interpose--interleave)
|
||||||
|
- **inline** - [8.2](en/02-advanced.md#82-transient-data-structures)
|
||||||
|
- **Installation** - [2.1](en/01-fundamentals.md#21-installation)
|
||||||
|
- **Integration testing** - [3.3](en/03-tooling.md#33-test-namespaces)
|
||||||
|
- **Interleave** - [7.3.8](en/01-fundamentals.md#738-interpose--interleave)
|
||||||
|
- **interpose** - [7.3.8](en/01-fundamentals.md#738-interpose--interleave)
|
||||||
|
- **into** - [7.3.7](en/01-fundamentals.md#737-interpose--interleave)
|
||||||
|
- **iterate** - [7.2](en/01-fundamentals.md#72-lazy-sequences)
|
||||||
|
|
||||||
|
## J
|
||||||
|
|
||||||
|
- **juxt** - [5.3](en/01-fundamentals.md#53-higher-order-functions)
|
||||||
|
|
||||||
|
## K
|
||||||
|
|
||||||
|
- **Keywords** - [4.5](en/01-fundamentals.md#45-keywords)
|
||||||
|
- **keys** - [8.2](en/01-fundamentals.md#82-map-destructuring)
|
||||||
|
|
||||||
|
## L
|
||||||
|
|
||||||
|
- **Lazy sequences** - [7.2](en/01-fundamentals.md#72-lazy-sequences), [2](en/02-advanced.md#2-lazy-sequences-deep-dive)
|
||||||
|
- **let** - [3.3.2](en/01-fundamentals.md#332-let)
|
||||||
|
- **letfn** - [5.1.3](en/01-fundamentals.md#513-variable-arguments)
|
||||||
|
- **Libraries** - [7](en/03-tooling.md#7-library-ecosystem)
|
||||||
|
- **Linters** - [5](en/03-tooling.md#5-code-quality-tools)
|
||||||
|
- **Lisp heritage** - [1.2.3](en/01-fundamentals.md#123-lisp-heritage)
|
||||||
|
- **Lists** - [4.7](en/01-fundamentals.md#47-lists)
|
||||||
|
- **load-file** - [15.2](en/01-fundamentals.md#152-repl-workflow)
|
||||||
|
- **local** - [3.2](en/03-tooling.md#32-fixtures-for-setupteardown)
|
||||||
|
- **Logging** - [8.6](en/03-tooling.md#86-logging)
|
||||||
|
- **loop** - [6.2.2](en/01-fundamentals.md#622-looprecur)
|
||||||
|
|
||||||
|
## M
|
||||||
|
|
||||||
|
- **macroexpand** - [10.5](en/01-fundamentals.md#105-macro-expansion)
|
||||||
|
- **Macros** - [10](en/01-fundamentals.md#10-macros)
|
||||||
|
- **Make** - [6.1](en/03-tooling.md#61-building-uberjars)
|
||||||
|
- **map** - [7.3.1](en/01-fundamentals.md#731-map)
|
||||||
|
- **map-indexed** - [7.3.1](en/01-fundamentals.md#731-map)
|
||||||
|
- **mapcat** - [7.3.5](en/01-fundamentals.md#735-mapcat)
|
||||||
|
- **mapv** - [7.3.1](en/01-fundamentals.md#731-map)
|
||||||
|
- **Maps** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
|
- **match-route** - [6.3](en/04-recipes.md#63-route-definitions)
|
||||||
|
- **max-key** - [5.3](en/01-fundamentals.md#53-higher-order-functions)
|
||||||
|
- **Maybe pattern** - [1.1](en/04-recipes.md#11-maybeoption-pattern)
|
||||||
|
- **Memoization** - [1.5](en/01-fundamentals.md#15-memoization), [8.6](en/02-advanced.md#86-preload-and-cache)
|
||||||
|
- **merge** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
|
- **merge-with** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
|
- **Metadata** - [1.7](en/02-advanced.md#17-function-metadata), [3.3.1](en/01-fundamentals.md#331-def)
|
||||||
|
- **min-key** - [5.3](en/01-fundamentals.md#53-higher-order-functions)
|
||||||
|
- **mod** - [4.1.1](en/01-fundamentals.md#411-integer-types)
|
||||||
|
- **multimethods** - [13](en/01-fundamentals.md#13-multimethods)
|
||||||
|
- **Mutability** - [11](en/01-fundamentals.md#11-concurrency)
|
||||||
|
|
||||||
|
## N
|
||||||
|
|
||||||
|
- **namespace** - [9.5](en/01-fundamentals.md#95-working-with-namespaces)
|
||||||
|
- **Namespaces** - [9](en/01-fundamentals.md#9-namespaces)
|
||||||
|
- **neg** - [4.2](en/01-fundamentals.md#42-strings)
|
||||||
|
- **nil** - [4.4](en/01-fundamentals.md#44-booleans)
|
||||||
|
- **not** - [4.4](en/01-fundamentals.md#44-booleans)
|
||||||
|
- **not-empty** - [4.12](en/01-fundamentals.md#412-collections-library)
|
||||||
|
- **ns** - [9.1](en/01-fundamentals.md#91-creating-and-switching-namespaces)
|
||||||
|
- **ns-publics** - [9.5](en/01-fundamentals.md#95-working-with-namespaces)
|
||||||
|
- **ns-resolve** - [9.5](en/01-fundamentals.md#95-working-with-namespaces)
|
||||||
|
- **nth** - [4.7](en/01-fundamentals.md#47-lists)
|
||||||
|
- **Numbers** - [4.1](en/01-fundamentals.md#41-numbers)
|
||||||
|
|
||||||
|
## O
|
||||||
|
|
||||||
|
- **or** - [3.5](en/01-fundamentals.md#35-special-forms)
|
||||||
|
|
||||||
|
## P
|
||||||
|
|
||||||
|
- **parallelize** - [11.7](en/01-fundamentals.md#117-stm-guidelines)
|
||||||
|
- **Parallelism** - [7](en/02-advanced.md#7-parallelism)
|
||||||
|
- **Parameter** - [5.1.3](en/01-fundamentals.md#513-variable-arguments)
|
||||||
|
- **parse** - [5.2](en/04-recipes.md#52-schema-validation)
|
||||||
|
- **partial** - [5.3](en/01-fundamentals.md#53-higher-order-functions)
|
||||||
|
- **partition** - [7.3.7](en/01-fundamentals.md#737-interpose--interleave)
|
||||||
|
- **partition-all** - [7.3.7](en/01-fundamentals.md#737-interpose--interleave)
|
||||||
|
- **partition-by** - [7.3.7](en/01-fundamentals.md#737-interpose--interleave)
|
||||||
|
- **peek** - [4.7](en/01-fundamentals.md#47-lists)
|
||||||
|
- **persist** - [7.2](en/01-fundamentals.md#72-lazy-sequences)
|
||||||
|
- **Persistent data structures** - [8.1](en/02-advanced.md#81-persistent-data-structures)
|
||||||
|
- **pheric** - [2.2](en/02-advanced.md#22-chunked-sequences)
|
||||||
|
- **pmap** - [7.1](en/02-advanced.md#71-pmap)
|
||||||
|
- **pop** - [4.8](en/01-fundamentals.md#48-vectors)
|
||||||
|
- **pos** - [4.2](en/01-fundamentals.md#42-strings)
|
||||||
|
- **postwalk** - [7.5](en/01-fundamentals.md#75-walking-collections)
|
||||||
|
- **preconditions** - [5.5](en/01-fundamentals.md#55-pre--and-post-conditions)
|
||||||
|
- **prewalk** - [7.5](en/01-fundamentals.md#75-walking-collections)
|
||||||
|
- **print** - [8.1](en/03-tooling.md#81-print-debugging)
|
||||||
|
- **println** - [4.2](en/01-fundamentals.md#42-strings)
|
||||||
|
- **Process** - [3.1](en/04-recipes.md#31-service-pattern-with-atoms)
|
||||||
|
- **profile** - [8.3](en/03-tooling.md#83-repl-debugging)
|
||||||
|
- **project** - [1](en/03-tooling.md#1-project-structure)
|
||||||
|
- **promise** - [11.6](en/01-fundamentals.md#116-promises-and-delivered)
|
||||||
|
- **Promises** - [11.6](en/01-fundamentals.md#116-promises-and-delivered)
|
||||||
|
- **Protocols** - [12](en/01-fundamentals.md#12-protocols-and-records)
|
||||||
|
- **put** - [16.1](en/01-fundamentals.md#161-channels)
|
||||||
|
|
||||||
|
## Q
|
||||||
|
|
||||||
|
- **quick-check** - [3.1](en/03-tooling.md#31-testcheck-for-generative-testing), [14.4](en/01-fundamentals.md#144-generative-testing)
|
||||||
|
|
||||||
|
## R
|
||||||
|
|
||||||
|
- **rand** - [7.4](en/01-fundamentals.md#74-creating-sequences)
|
||||||
|
- **Random** - [7.4](en/01-fundamentals.md#74-creating-sequences)
|
||||||
|
- **range** - [7.4](en/01-fundamentals.md#74-creating-sequences)
|
||||||
|
- **Ratio** - [4.1.3](en/01-fundamentals.md#413-ratios)
|
||||||
|
- **Readers** - [2.1](en/03-tooling.md#21-depsedn-reference)
|
||||||
|
- **realized** - [2.1](en/02-advanced.md#21-realizing-sequences)
|
||||||
|
- **reduced** - [3.4](en/02-advanced.md#34-early-termination)
|
||||||
|
- **Reducers** - [6](en/02-advanced.md#6-reducibles)
|
||||||
|
- **reduce** - [7.3.3](en/01-fundamentals.md#733-reduce)
|
||||||
|
- **reduce-kv** - [7.3.3](en/01-fundamentals.md#733-reduce)
|
||||||
|
- **reductions** - [7.3.3](en/01-fundamentals.md#733-reduce)
|
||||||
|
- **Ref** - [11.2](en/01-fundamentals.md#112-refs)
|
||||||
|
- **ref-set** - [11.2](en/01-fundamentals.md#112-refs)
|
||||||
|
- **refresh** - [4.1](en/03-tooling.md#41-repl-driven-development)
|
||||||
|
- **release** - [11.3](en/01-fundamentals.md#113-agents)
|
||||||
|
- **remove** - [7.3.2](en/01-fundamentals.md#732-filter--remove)
|
||||||
|
- **remove-method** - [13.4](en/01-fundamentals.md#134-remove-method)
|
||||||
|
- **repeat** - [7.4](en/01-fundamentals.md#74-creating-sequences)
|
||||||
|
- **repeatedly** - [7.4](en/01-fundamentals.md#74-creating-sequences)
|
||||||
|
- **require** - [9.3](en/01-fundamentals.md#93-referring-and-importing)
|
||||||
|
- **require** - [4.1](en/03-tooling.md#41-repl-driven-development)
|
||||||
|
- **reset!** - [11.1](en/01-fundamentals.md#111-atoms)
|
||||||
|
- **rest** - [4.7](en/01-fundamentals.md#47-lists)
|
||||||
|
- **REPL** - [2.2](en/01-fundamentals.md#22-your-first-clojure-project), [15](en/01-fundamentals.md#15-the-repl)
|
||||||
|
- **rest** - [7.3.6](en/01-fundamentals.md#736-take--drop)
|
||||||
|
- **reverse** - [7.3.9](en/01-fundamentals.md#739-distinct--sort--shuffle)
|
||||||
|
|
||||||
|
## S
|
||||||
|
|
||||||
|
- **s/conform** - [8.2](en/03-tooling.md#82-stack-traces)
|
||||||
|
- **s/gen** - [4.5](en/02-advanced.md#45-generative-testing)
|
||||||
|
- **sample** - [4.5](en/02-advanced.md#45-generative-testing)
|
||||||
|
- **Schema validation** - [5.2](en/04-recipes.md#52-schema-validation)
|
||||||
|
- **select-keys** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
|
- **send** - [11.3](en/01-fundamentals.md#113-agents)
|
||||||
|
- **send-off** - [11.3](en/01-fundamentals.md#113-agents)
|
||||||
|
- **seq** - [7.1](en/01-fundamentals.md#71-the-sequence-abstraction)
|
||||||
|
- **Sequences** - [7](en/01-fundamentals.md#7-sequences-and-lazy-evaluation)
|
||||||
|
- **seqable** - [2.4](en/02-advanced.md#24-seqable-objects)
|
||||||
|
- **Service pattern** - [3.1](en/04-recipes.md#31-service-pattern-with-atoms)
|
||||||
|
- **set** - [4.10](en/01-fundamentals.md#410-sets)
|
||||||
|
- **set!** - [11.4](en/01-fundamentals.md#114-vars)
|
||||||
|
- **Shallow** - [8.1](en/02-advanced.md#81-persistent-data-structures)
|
||||||
|
- **shuffle** - [7.3.9](en/01-fundamentals.md#739-distinct--sort--shuffle)
|
||||||
|
- **shutdown** - [11.3](en/01-fundamentals.md#113-agents)
|
||||||
|
- **some** - [7.3.2](en/01-fundamentals.md#732-filter--remove)
|
||||||
|
- **some->** - [17.6](en/01-fundamentals.md#176-threading-macros)
|
||||||
|
- **some-fn** - [5.3](en/01-fundamentals.md#53-higher-order-functions)
|
||||||
|
- **sort** - [7.3.9](en/01-fundamentals.md#739-distinct--sort--shuffle)
|
||||||
|
- **sort-by** - [7.3.9](en/01-fundamentals.md#739-distinct--sort--shuffle)
|
||||||
|
- **Source tracking** - [4.3](en/03-tooling.md#43-source-tracking)
|
||||||
|
- **spec** - [4](en/02-advanced.md#4-specs-and-validation)
|
||||||
|
- **split-at** - [7.3.6](en/01-fundamentals.md#736-take--drop)
|
||||||
|
- **split-with** - [7.3.6](en/01-fundamentals.md#736-take--drop)
|
||||||
|
- **Stack traces** - [8.2](en/03-tooling.md#82-stack-traces)
|
||||||
|
- **State machine** - [1.3](en/04-recipes.md#13-state-machine-pattern)
|
||||||
|
- **STM** - [11.2](en/01-fundamentals.md#112-refs)
|
||||||
|
- **stop** - [3.1](en/04-recipes.md#31-service-pattern-with-atoms)
|
||||||
|
- **str** - [4.2](en/01-fundamentals.md#42-strings)
|
||||||
|
- **Strings** - [4.2](en/01-fundamentals.md#42-strings)
|
||||||
|
- **Structural sharing** - [8.1](en/02-advanced.md#81-persistent-data-structures)
|
||||||
|
- **subs** - [4.2](en/01-fundamentals.md#42-strings)
|
||||||
|
- **subvec** - [4.8](en/01-fundamentals.md#48-vectors)
|
||||||
|
- **superiors** - [13.3](en/01-fundamentals.md#133-hierarchies)
|
||||||
|
- **swap!** - [11.1](en/01-fundamentals.md#111-atoms)
|
||||||
|
- **Symbols** - [4.6](en/01-fundamentals.md#46-symbols)
|
||||||
|
|
||||||
|
## T
|
||||||
|
|
||||||
|
- **TAP** - [8.1](en/03-tooling.md#81-print-debugging)
|
||||||
|
- **take** - [7.3.6](en/01-fundamentals.md#736-take--drop)
|
||||||
|
- **take-nth** - [7.3.6](en/01-fundamentals.md#736-take--drop)
|
||||||
|
- **take-while** - [7.3.6](en/01-fundamentals.md#736-take--drop)
|
||||||
|
- **test** - [3.3](en/03-tooling.md#33-test-namespaces), [14.1](en/01-fundamentals.md#141-clojuretest)
|
||||||
|
- **test.check** - [14.4](en/01-fundamentals.md#144-generative-testing)
|
||||||
|
- **Testing** - [14](en/01-fundamentals.md#14-testing)
|
||||||
|
- **test** - [4.5](en/02-advanced.md#45-generative-testing)
|
||||||
|
- **thread-bound** - [11.4](en/01-fundamentals.md#114-vars)
|
||||||
|
- **Thread-first** - [17.6](en/01-fundamentals.md#176-threading-macros)
|
||||||
|
- **Thread-last** - [17.6](en/01-fundamentals.md#176-threading-macros)
|
||||||
|
- **throw** - [6.3](en/01-fundamentals.md#63-exception-handling)
|
||||||
|
- **Timeout** - [4.3](en/04-recipes.md#43-timeout-patterns)
|
||||||
|
- **to** - [4.6](en/01-fundamentals.md#46-symbols)
|
||||||
|
- **trace** - [8.5](en/03-tooling.md#85-breakpoints)
|
||||||
|
- **track** - [4.3](en/03-tooling.md#43-source-tracking)
|
||||||
|
- **Transducers** - [3](en/02-advanced.md#3-transducers)
|
||||||
|
- **transient** - [8.2](en/02-advanced.md#82-transient-data-structures)
|
||||||
|
- **Transitive** - [13.3](en/01-fundamentals.md#133-hierarchies)
|
||||||
|
- **Tree operations** - [2.4](en/04-recipes.md#24-tree-operations)
|
||||||
|
- **tree-seq** - [7.5](en/01-fundamentals.md#75-walking-collections)
|
||||||
|
- **try** - [6.3](en/01-fundamentals.md#63-exception-handling)
|
||||||
|
- **Type checking** - [5.4](en/03-tooling.md#54-typed-clojure-optional-type-checking)
|
||||||
|
- **type** - [12.2](en/01-fundamentals.md#122-records)
|
||||||
|
|
||||||
|
## U
|
||||||
|
|
||||||
|
- **uberjar** - [6.1](en/03-tooling.md#61-building-uberjars)
|
||||||
|
- **Unquote** - [10.3](en/01-fundamentals.md#103-unquoting)
|
||||||
|
- **update** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
|
- **update-in** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
|
- **use** - [9.3](en/01-fundamentals.md#93-referring-and-importing)
|
||||||
|
- **use-fixtures** - [3.2](en/03-tooling.md#32-fixtures-for-setupteardown)
|
||||||
|
|
||||||
|
## V
|
||||||
|
|
||||||
|
- **val** - [7.1](en/01-fundamentals.md#71-the-sequence-abstraction)
|
||||||
|
- **validate** - [5.1](en/04-recipes.md#51-multi-stage-validation)
|
||||||
|
- **Validation** - [5](en/04-recipes.md#5-validation-recipes)
|
||||||
|
- **vals** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
|
- **var** - [3.3.1](en/01-fundamentals.md#331-def)
|
||||||
|
- **var-get** - [11.4](en/01-fundamentals.md#114-vars)
|
||||||
|
- **Vars** - [11.4](en/01-fundamentals.md#114-vars)
|
||||||
|
- **vec** - [4.8](en/01-fundamentals.md#48-vectors)
|
||||||
|
- **Vector** - [4.8](en/01-fundamentals.md#48-vectors)
|
||||||
|
- **vector-of** - [4.8](en/01-fundamentals.md#48-vectors)
|
||||||
|
- **volatile** - [11.1](en/01-fundamentals.md#111-atoms)
|
||||||
|
|
||||||
|
## W
|
||||||
|
|
||||||
|
- **when** - [6.1.2](en/01-fundamentals.md#612-when--when-not)
|
||||||
|
- **when-bind** - [10.6.2](en/01-fundamentals.md#1062-conditional-compilation)
|
||||||
|
- **when-first** - [6.1.2](en/01-fundamentals.md#612-when--when-not)
|
||||||
|
- **when-let** - [6.1.2](en/01-fundamentals.md#612-when--when-not)
|
||||||
|
- **when-not** - [6.1.2](en/01-fundamentals.md#612-when--when-not)
|
||||||
|
- **while** - [6.2](en/01-fundamentals.md#62-iteration)
|
||||||
|
- **Whitespace** - [3.6](en/01-fundamentals.md#36-whitespace-and-formatting)
|
||||||
|
- **Windowing** - [4.4](en/04-recipes.md#44-windowing)
|
||||||
|
|
||||||
|
## Z
|
||||||
|
|
||||||
|
- **zero** - [4.2](en/01-fundamentals.md#42-strings)
|
||||||
|
- **zipmap** - [4.9](en/01-fundamentals.md#49-maps)
|
||||||
Reference in New Issue
Block a user