diff --git a/.gitignore b/.gitignore index 56f76bf..455d262 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ test_deps test_eval test_macros test_ai_assist -books/ + diff --git a/books/clojure-book/README.md b/books/clojure-book/README.md new file mode 100644 index 0000000..0b15434 --- /dev/null +++ b/books/clojure-book/README.md @@ -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.* diff --git a/books/clojure-book/bg/01-fundamentals.md b/books/clojure-book/bg/01-fundamentals.md new file mode 100644 index 0000000..353372e --- /dev/null +++ b/books/clojure-book/bg/01-fundamentals.md @@ -0,0 +1,2033 @@ +# Чист Clojure: Изчерпателно Ръководство + +## Съдържание + +1. [Въведение в Clojure](#1-въведение-в-clojure) +2. [Първи стъпки](#2-първи-стъпки) +3. [Основен синтаксис и форми](#3-основен-синтаксис-и-форми) +4. [Структури от данни](#4-структури-от-данни) +5. [Функции](#5-функции) +6. [Управление на потока](#6-управление-на-потока) +7. [Серии и мързеливо оценяване](#7-серии-и-мързеливо-оценяване) +8. [Деструктуриране](#8-деструктуриране) +9. [Пространства от имена](#9-пространства-от-имена) +10. [Макроси](#10-макроси) +11. [Конкурентност](#11-конкурентност) +12. [Протоколи и записи](#12-протоколи-и-записи) +13. [Многометодни функции](#13-многометодни-функции) +14. [Тестване](#14-тестване) +15. [REPL](#15-repl) +16. [Core.async](#16-coreasync) +17. [Добри практики](#17-добри-практики) +18. [Индекс](#18-индекс) + +--- + +## 1. Въведение в Clojure + +### 1.1 Какво е Clojure? + +Clojure е съвременен, динамичен и функционален език за програмиране, който работи на Java Virtual Machine (JVM), .NET Common Language Runtime (CLR) и JavaScript двигатели чрез ClojureScript. Създаден от Rich Hickey през 2007 г., Clojure е диалект на Lisp, който набляга на неизменяемостта, функционалното програмиране и кратката изразителност. + +### 1.2 Ключови характеристики на Clojure + +#### 1.2.1 Неизменяемост по подразбиране + +В Clojure всички структури от данни са неизменяеми по подразбиране. Когато "модифицирате" структура от данни, вие всъщност създавате нова версия с желаните промени, докато оригиналът остава непроменен. Този подход води до по-безопасни конкурентни програми и по-чист код. + +```clojure +(def original [1 2 3]) +(def modified (conj original 4)) +;; original => [1 2 3] +;; modified => [1 2 3 4] +``` + +#### 1.2.2 Функционално програмиране + +Clojure насърчава чисти функции без странични ефекти. Функциите са обекти от първи клас, които могат да бъдат предавани като аргументи, връщани от други функции и композирани заедно. + +```clojure +(def double (partial * 2)) +(def add-ten (partial + 10)) +(def transform (comp double add-ten)) +(transform 5) ;; => 30 +``` + +#### 1.2.3 Наследство от Lisp + +Като диалект на Lisp, Clojure наследява силата на макросите и еднаквото представяне на код като данни. Всичко е израз, който връща стойност, а синтаксисът е прост и последователен. + +#### 1.2.4 Полиморфизъм по време на изпълнение + +Clojure предоставя множество механизми за полиморфизъм: +- **Протоколи**: Дефинират сигнатури на методи за типове данни +- **Многометодни функции**: Диспечират въз основа на произволни критерии +- **Записи**: Конкретни типове данни, които имплементират протоколи + +### 1.3 Защо Чист Clojure? + +Въпреки че Clojure работи на JVM и има отлична Java интеграция, тази книга се фокусира върху **чист Clojure** — основните функции на езика, които не разчитат на Java интеграция. Този подход: + +- Учи фундаменталните концепции на Clojure +- Прави кода преносим (включително ClojureScript) +- Насърчава мисленето в парадигмата на Clojure +- Избягва ненужно смесване на парадигми + +### 1.4 Философията на Clojure + +Clojure се придържа към няколко принципа: + +1. **Простота**: Трудните неща трябва да бъдат прости, простите неща трябва да бъдат тривиални +2. **Неизменяемост**: Предпочитайте неизменяеми структури от данни за безопасност и конкурентност +3. **Абстракция**: Изграждайте слоеве на абстракция за управление на сложността +4. **Ориентираност към изрази**: Всичко е израз, който връща стойност + +--- + +## 2. Първи стъпки + +### 2.1 Инсталация + +#### 2.1.1 Използване на CLI инструменти + +Препоръчителният начин за инсталиране на Clojure е чрез официалния CLI инструмент: + +**Linux/macOS:** +```bash +curl -O https://download.clojure.org/install/linux-install-1.11.1.1202.sh +chmod +x linux-install-1.11.1.1202.sh +./linux-install-1.11.1.1202.sh +``` + +**macOS (Homebrew):** +```bash +brew install clojure +``` + +#### 2.1.2 Ръчна инсталация + +Изтеглете Clojure JAR файла и го използвайте директно: +```bash +java -jar clojure-1.11.1.jar +``` + +### 2.2 Вашият първи Clojure проект + +#### 2.2.1 Създаване на проект с deps.edn + +Създайте нова директория за вашия проект и добавете файл `deps.edn`: + +```clojure +{:deps {org.clojure/clojure {:mvn/version "1.11.1"}}} +``` + +Стартирайте Clojure: +```bash +clj -M +``` + +#### 2.2.2 Основи на REPL + +REPL (Read-Eval-Print Loop) е вашата основна среда за разработка: + +```clojure +user=> (+ 1 2 3) +6 +user=> (println "Hello, Clojure!") +Hello, Clojure! +nil +user=> (def message "Hello, World!") +#'user/message +user=> message +"Hello, World!" +``` + +### 2.3 Настройка на редактора + +Популярни редактори за Clojure разработка: + +- **VS Code**: Разширение Calva +- **Emacs**: Режим CIDER +- **Vim/Neovim**: Добавка Conjure +- **IntelliJ**: Добавка Cursive + +--- + +## 3. Основен синтаксис и форми + +### 3.1 S-Изрази + +Кодът на Clojure се пише като **s-изрази** (символни изрази), които са вложени списъци: + +```clojure +(оператор операнди...) +``` + +Първият елемент е операторът (функция, макрос или специална форма), а останалите са операнди. + +```clojure +(+ 1 2) ;; Събиране: 3 +(* 2 3 4) ;; Умножение: 24 +(< 1 2 3) ;; Сравнение: true +(and true false) ;; Логическо И: false +``` + +### 3.2 Данни като код + +В Lisp данните и кодът са едно и също нещо. Това означава, че можете да манипулирате програмите си като данни: + +```clojure +'(+ 1 2 3) ;; Цитиран списък: (+ 1 2 3) +(list + 1 2) ;; Списък със + функцията и числа +``` + +### 3.3 Специални форми + +Специалните форми са примитиви, които не могат да бъдат изразени като функции, защото имат специални правила за оценяване. + +#### 3.3.1 def + +Дефинира глобална променлива: + +```clojure +(def x 10) +(def name "Clojure") +(def items [1 2 3]) +``` + +#### 3.3.2 let + +Създава локални свързвания: + +```clojure +(let [x 10 + y 20] + (+ x y)) ;; => 30 +``` + +#### 3.3.3 if + +Условно изпълнение: + +```clojure +(if условие + тогава-израз + else-израз) +``` + +#### 3.3.4 quote + +Предотвратява оценяване: + +```clojure +(quote (+ 1 2)) ;; => (+ 1 2) +'(+ 1 2) ;; Кратка форма: (+ 1 2) +``` + +### 3.4 Правила за оценяване + +1. Числа, низове, булеви стойности, nil и ключови думи **се оценяват като себе си** +2. Символи **се оценяват като стойността** на променливата, която именуват +3. Списъци **се оценяват като извиквания на функции** (ако първият елемент е извикаем) +4. Цитираните изрази **предотвратяват оценяване** + +### 3.5 Коментари + +```clojure +;; Едноредов коментар + +;; Многоредов коментар +;; (няма специален многоредов синтаксис, +;; просто използвайте няколко едноредови коментара) + +(comment + "Това е коментар блок, който няма да бъде оценен" + (+ 1 2)) +``` + +### 3.6 Интервали и форматиране + +- Clojure е безразличен към интервалите (с изключение в рамките на символи) +- Стандартна конвенция: една интервал след отваряща скоба, преди затваряща +- Подравнявайте аргументите вертикално за четливост: + +```clojure +(do-something arg1 + arg2 + arg3) +``` + +--- + +## 4. Структури от данни + +Clojure предоставя богат набор от неизменяеми структури от данни. Разбирането им е фундаментално за писане на идиоматичен Clojure. + +### 4.1 Числа + +#### 4.1.1 Целочислени типове + +```clojure +42 ;; Десетично +017 ;; Осмично (15) +0x2A ;; Шестнадесетично (42) +2r101010 ;; Двоично (42) +``` + +#### 4.1.2 Числа с плаваща запетая + +```clojure +3.14 +6.022e23 +``` + +#### 4.1.3 Рационални числа + +Clojure запазва точността с рационални числа: + +```clojure +1/3 ;; Тип рационално +22/7 ;; Приближение на pi +(/ 1 3) ;; 1/3 +``` + +### 4.2 Низове + +```clojure +"Hello, World!" +"Multi-line +string" + +;; Конкатенация +(str "Hello" " " "World") ;; => "Hello World" + +;; Подниз +(subs "Hello" 0 5) ;; => "Hello" + +;; Функции за низове +(count "Clojure") ;; => 7 +(reverse "Clojure") ;; => "erujolC" +``` + +### 4.3 Символи (Characters) + +```clojure +\a ;; Символ a +\newline ;; Нов ред +\space ;; Интервал +``` + +### 4.4 Булеви стойности + +```clojure +true +false +nil ;; Представлява отсъствие на стойност +``` + +Правила за истинност: +- Всичко с изключение на `false` и `nil` е истина +- `and`, `or`, `if`, `when` използват това правило + +### 4.5 Ключови думи (Keywords) + +Ключовите думи са интернирани низове, използвани като идентификатори, често за ключове в map-ове: + +```clojure +:foo +:bar +:user/name ;; Пространство от имена на ключова дума +::local-key ;; Автоматично с пространство от имена +``` + +Ключовите думи се оценяват като себе си и могат да се използват като функции за търсене на стойности в map-ове. + +### 4.6 Символи (Symbols) + +Символите се оценяват като променливите, които именуват: + +```clojure +'x ;; Символ x (цитиран) +(def x 10) ;; Дефинира променлива x със стойност 10 +x ;; Оценява се до 10 +``` + +### 4.7 Списъци (Lists) + +Списъците са свързани списъци, ефективни за последователен достъп в началото: + +```clojure +'(1 2 3) ;; Цитирайте, за да предотвратите оценяване +(list 1 2 3) ;; Създава списък +'(+ 1 2) ;; Списък съдържащ символа + + +;; Достъп +(first '(1 2 3)) ;; => 1 +(second '(1 2 3)) ;; => 2 +(rest '(1 2 3)) ;; => (2 3) +(nth '(1 2 3) 0) ;; => 1 + +;; Модификация (връща нов списък) +(cons 0 '(1 2 3)) ;; => (0 1 2 3) +(concat '(1 2) '(3 4)) ;; => (1 2 3 4) +``` + +### 4.8 Вектори (Vectors) + +Векторите са индексирани колекции, ефективни за случаен достъп: + +```clojure +[1 2 3 4 5] +(vector 1 2 3) ;; => [1 2 3] + +;; Достъп +(get [10 20 30] 1) ;; => 20 +([10 20 30] 1) ;; => 20 (достъп като с ключова дума) +(first [1 2 3]) ;; => 1 +(second [1 2 3]) ;; => 2 +(last [1 2 3]) ;; => 3 + +;; Модификация (връща нов вектор) +(conj [1 2] 3) ;; => [1 2 3] +(pop [1 2 3]) ;; => [1 2] +(assoc [1 2 3] 1 20) ;; => [1 20 3] +(subvec [1 2 3 4 5] 1 3) ;; => [2 3] +``` + +### 4.9 Map-ове (Maps) + +Map-овете са асоциативни структури ключ-стойност: + +```clojure +{:name "Alice" :age 30} +(hash-map :a 1 :b 2 :c 3) +(assoc {:a 1} :b 2) ;; => {:a 1 :b 2} +(dissoc {:a 1 :b 2} :a) ;; => {:b 2} +(get {:a 1} :a) ;; => 1 +({:a 1} :a) ;; => 1 +(:a {:a 1}) ;; => 1 (ключовите думи са функции!) + +;; Вложен достъп +(get-in {:user {:address {:city "Sofia"}}} + [:user :address :city]) ;; => "Sofia" + +;; Сливане +(merge {:a 1} {:b 2} {:c 3}) ;; => {:a 1 :b 2 :c 3} +``` + +### 4.10 Множества (Sets) + +Множествата са колекции от уникални стойности: + +```clojure +#{1 2 3} +(hash-set 1 2 3 2 1) ;; => #{1 2 3} +(set [1 2 2 3 3 3]) ;; => #{1 2 3} + +;; Операции +(conj #{1 2} 3) ;; => #{1 2 3} +(disj #{1 2 3} 2) ;; => #{1 3} +(contains? #{1 2 3} 2) ;; => true +(get #{1 2 3} 2) ;; => 2 +(clojure.set/union #{1 2} #{2 3}) ;; => #{1 2 3} +(clojure.set/intersection #{1 2 3} #{2 3 4}) ;; => #{2 3} +(clojure.set/difference #{1 2 3} #{2 3}) ;; => #{1} +``` + +### 4.11 Структуриране на данни + +```clojure +;; Представяне на потребител с map +(def user {:name "John" + :email "john@example.com" + :roles [:admin :user]}) + +;; Вложени данни +(def company {:name "TechCorp" + :employees [{:name "Alice" :dept "Engineering"} + {:name "Bob" :dept "Sales"}] + :locations {:HQ "New York" + :branch "Boston"}}) +``` + +### 4.12 Библиотека за колекции + +Основни функции за колекции, които работят еднакво върху различни структури: + +```clojure +;; Предикати +(empty? []) ;; => true +(empty? [1 2 3]) ;; => false +(every? even? [2 4 6]) ;; => true +(some odd? [2 4 5 6]) ;; => true +(not-empty [1 2 3]) ;; => [1 2 3] +(not-empty []) ;; => nil + +;; Брой +(count [1 2 3]) ;; => 3 +(count {:a 1 :b 2}) ;; => 2 + +;; Конверсия +(vec '(1 2 3)) ;; => [1 2 3] +(list [1 2 3]) ;; => (1 2 3) +(set [1 2 2 3]) ;; => #{1 2 3} +(mapv inc [1 2 3]) ;; => [2 3 4] +``` + +--- + +## 5. Функции + +### 5.1 Дефиниране на функции + +#### 5.1.1 Основен синтаксис + +```clojure +(defn greeting + "Връща поздравително съобщение" + [name] + (str "Hello, " name "!")) + +(greeting "World") ;; => "Hello, World!" +``` + +#### 5.1.2 Множество арности + +Функциите могат да имат различен брой аргументи: + +```clojure +(defn add + ([x] (add x 0)) + ([x y] (+ x y)) + ([x y z] (+ x y z))) + +(add 5) ;; => 5 +(add 5 3) ;; => 8 +(add 1 2 3) ;; => 6 +``` + +#### 5.1.3 Променлив брой аргументи + +Използвайте `&` за останали параметри: + +```clojure +(defn sum [& numbers] + (reduce + numbers)) + +(sum 1 2 3 4 5) ;; => 15 +``` + +### 5.2 Анонимни функции + +```clojure +(fn [x] (* x x)) +#(* % %) ;; Имплицитен аргумент +#(* %1 %2) ;; Множество аргументи +#(reduce + %&) ;; Останали аргументи +``` + +### 5.3 Функции от по-висок ред + +Функции, които приемат или връщат други функции: + +```clojure +(def double #( * % 2)) +(def square #(* % %)) + +(map double [1 2 3 4]) ;; => (2 4 6 8) +(map square [1 2 3 4]) ;; => (1 4 9 16) + +(filter even? [1 2 3 4 5 6]) ;; => (2 4 6) + +(reduce + [1 2 3 4 5]) ;; => 15 +(reduce max [3 1 4 1 5]) ;; => 5 + +;; Композиране на функции +(def transform (comp square double)) +(transform 3) ;; => 36 (3*2=6, 6*6=36) +``` + +### 5.4 Затваряния (Closures) + +Функции, които улавят своята среда: + +```clojure +(defn make-adder [x] + (fn [y] (+ x y))) + +(def add-5 (make-adder 5)) +(add-5 10) ;; => 15 +(add-5 3) ;; => 8 + +;; Пример за брояч +(defn make-counter [] + (let [count (atom 0)] + {:increment #(swap! count inc) + :decrement #(swap! count dec) + :value #(deref count)})) +``` + +### 5.5 Пред- и пост-условия + +```clojure +(defn absolute-value [n] + {:pre [(number? n)] + :post [(>= % 0)]} + (if (neg? n) + (- n) + n)) +``` + +### 5.6 Многометодни функции чрез defn + +Въпреки че истинските многометодни функции използват `defmulti` и `defmethod`, обикновените функции могат да симулират dispatch по поведение: + +```clojure +(defn process [x] + (cond + (string? x) (clojure.string/upper-case x) + (number? x) (inc x) + :else "unknown")) +``` + +--- + +## 6. Управление на потока + +### 6.1 Разклоняване + +#### 6.1.1 if / if-not + +```clojure +(if условие + тогава-израз + else-израз) + +(if (pos? -5) + "положително" + "не е положително") ;; => "не е положително" + +;; if-not е просто (if (not условие)...) +(if-not (even? 4) + "нечетно" + "четно") ;; => "четно" +``` + +#### 6.1.2 when / when-not + +Единично разклонение без else: + +```clojure +(when (pos? 5) + (println "Положително!") + (inc 5)) ;; => 6 + +(when-not (neg? 3) + "неотрицателно") ;; => "неотрицателно" +``` + +#### 6.1.3 cond + +Множество условия: + +```clojure +(defn classify [n] + (cond + (neg? n) "отрицателно" + (zero? n) "нула" + (even? n) "положително четно" + :else "положително нечетно")) + +(classify -5) ;; => "отрицателно" +(classify 0) ;; => "нула" +(classify 4) ;; => "положително четно" +(classify 7) ;; => "положително нечетно" +``` + +#### 6.1.4 condp + +Dispatch базиран на предикат: + +```clojure +(defn respond [msg] + (condp = msg + "hello" "Здравей!" + "bye" "Довиждане!" + "how are you?" "Добре!" + "Неизвестно съобщение")) + +(respond "hello") ;; => "Здравей!" +``` + +#### 6.1.5 case + +Dispatch с константно време (използва хеш сравнение): + +```clojure +(defn http-status [code] + (case code + 200 "OK" + 301 "Преместен постоянно" + 404 "Не е намерен" + 500 "Вътрешна грешка на сървъра" + "Неизвестен")) + +(http-status 200) ;; => "OK" +(http-status 999) ;; => "Неизвестен" +``` + +### 6.2 Итерация + +#### 6.2.1 Рекурсия + +```clojure +(defn factorial [n] + (if (<= n 1) + 1 + (* n (factorial (dec n))))) + +;; Със recur (оптимизирано за опашка) +(defn factorial [n] + (letfn [(fac [n acc] + (if (<= n 1) + acc + (recur (dec n) (* acc n))))] + (fac n 1))) +``` + +#### 6.2.2 loop/recur + +Явна итерация с опашкова рекурсия: + +```clojure +(loop [i 0 + result []] + (if (= i 10) + result + (recur (inc i) (conj result i)))) + +;; => [0 1 2 3 4 5 6 7 8 9] +``` + +#### 6.2.3 for (list comprehension) + +```clojure +(for [x (range 5) + :let [y (* x x)] + :when (even? y)] + y) +;; => (0 4 16) + +(for [x [:a :b :c] + y [1 2]] + [x y]) +;; => ([a 1] [a 2] [b 1] [b 2] [c 1] [c 2]) +``` + +#### 6.2.4 doseq (странични ефекти) + +```clojure +(doseq [x (range 3) + y (range 3)] + (println [x y])) +;; Отпечатва: +;; [0 0] +;; [0 1] +;; [0 2] +;; ... +``` + +### 6.3 Обработка на изключения + +```clojure +(try + (/ 1 0) + (catch ArithmeticException e + (str "Грешка: " (.getMessage e))) + (finally + (println "Почистване"))) + +;; С throw +(try + (throw (ex-info "Потребителска грешка" {:code 123})) + (catch Exception e + (ex-data e))) ;; => {:code 123} +``` + +### 6.4 do + +Изпълнява множество изрази, връща последния: + +```clojure +(do + (println "Страничен ефект") + (println "Още един") + (+ 1 2)) ;; => 3 +``` + +--- + +## 7. Серии и мързеливо оценяване + +### 7.1 Абстракцията Серия (Sequence) + +Clojure предоставя унифициран интерфейс за последователни колекции. Ключовите функции са: +- `first` - Първи елемент +- `rest` - Всички елементи след първия +- `cons` - Добавя елемент в началото + +```clojure +;; Работи върху списъци, вектори, низове, map-ове, множества и т.н. +(first [1 2 3]) ;; => 1 +(rest [1 2 3]) ;; => (2 3) +(cons 0 [1 2 3]) ;; => (0 1 2 3) + +(first "hello") ;; => \h +(rest "hello") ;; => (\e \l \l \o) +(first {:a 1 :b 2}) ;; => [:a 1] +``` + +### 7.2 Мързеливи серии + +Мързеливите серии се изчисляват при поискване, което позволява: +-безкрайни серии +- ефективност на паметта +- оптимизация на производителността + +```clojure +;; range произвежда безкрайна мързелива серия +(take 10 (range)) ;; => (0 1 2 3 4 5 6 7 8 9) + +;; Поредица на Фибоначи +(def fibs + (lazy-cat [0 1] (map + fibs (rest fibs)))) + +(take 10 fibs) ;; => (0 1 1 2 3 5 8 13 21 34) + +;; iterate +(take 5 (iterate inc 0)) ;; => (0 1 2 3 4) +(take 5 (iterate #(* 2 %) 1)) ;; => (1 2 4 8 16) +``` + +### 7.3 Функции за серии + +#### 7.3.1 map + +Трансформира всеки елемент: + +```clojure +(map inc [1 2 3]) ;; => (2 3 4) +(map + [1 2 3] [4 5 6]) ;; => (5 7 9) +(map str "abc") ;; => ("a" "b" "c") +``` + +#### 7.3.2 filter / remove + +Селектира/отхвърля елементи: + +```clojure +(filter even? (range 10)) ;; => (0 2 4 6 8) +(remove even? (range 10)) ;; => (1 3 5 7 9) +(filterv even? (range 10)) ;; => [0 2 4 6 8] (вектор) +``` + +#### 7.3.3 reduce + +Обработва елементи с натрупване: + +```clojure +(reduce + [1 2 3 4 5]) ;; => 15 +(reduce + 10 [1 2 3]) ;; => 16 (с начална стойност) +(reduce (fn [[sum cnt] x] + [(+ sum x) (inc cnt)]) + [0 0] + [1 2 3 4 5]) +;; => [15 5] +``` + +#### 7.3.4 fold + +Паралелно намаляване (използва reducers): + +```clojure +(require '[clojure.core.reducers :as r]) +(r/fold + (range 1000)) +``` + +#### 7.3.5 mapcat + +Map-ва и след това изравнява: + +```clojure +(mapcat reverse [[1 2] [3 4] [5 6]]) ;; => (2 1 4 3 6 5) +``` + +#### 7.3.6 take / drop + +```clojure +(take 3 (range 10)) ;; => (0 1 2) +(drop 3 (range 10)) ;; => (3 4 5 6 7 8 9) +(take-while pos? [3 2 1 0 -1]) ;; => (3 2 1) +(drop-while pos? [3 2 1 0 -1]) ;; => (0 -1) +(split-at 3 (range 5)) ;; => [(0 1 2) (3 4)] +``` + +#### 7.3.7 flatten / partition + +```clojure +(flatten [[1 2] [3 [4 5]]]) ;; => (1 2 3 4 5) +(partition 2 (range 8)) ;; => ((0 1) (2 3) (4 5) (6 7)) +(partition-all 3 (range 10)) ;; => ((0 1 2) (3 4 5) (6 7 8) (9)) +(partition-by even? [1 2 3 4 5 6]) ;; => ((1) (2 3 4) (5 6)) +``` + +#### 7.3.8 Interpose / Interleave + +```clojure +(interpose "," ["a" "b" "c"]) ;; => ("a" "," "b" "," "c") +(apply str (interpose "," ["a" "b" "c"])) ;; => "a,b,c" +(interleave [1 2 3] [:a :b :c]) ;; => (1 :a 2 :b 3 :c) +``` + +#### 7.3.9 distinct / sort / shuffle + +```clojure +(distinct [1 2 2 3 3 3 4]) ;; => (1 2 3 4) +(sort [3 1 4 1 5 9 2]) ;; => (1 1 2 3 4 5 9) +(sort-by :age [{:age 30} {:age 20} {:age 40}]) +;; => ({:age 20} {:age 30} {:age 40}) +(shuffle (range 5)) ;; => случайна наредба +``` + +### 7.4 Създаване на серии + +```clojure +(range) ;; Безкрайни 0, 1, 2, ... +(range 5) ;; (0 1 2 3 4) +(range 1 10 2) ;; (1 3 5 7 9) start, end, step +(repeat 5 :x) ;; (:x :x :x :x :x) +(repeatedly 5 #(rand-int 100)) ;; Случайни стойности +(cycle [:a :b]) ;; Безкрайни (:a :b :a :b ...) +``` + +### 7.5 Обхождане на колекции + +```clojure +;; tree-seq: обхожда вложена структура +(tree-seq sequential? seq [1 [2 [3 4]] 5]) +;; => ([1 [2 [3 4]] 5] 1 [2 [3 4]] 2 [3 4] 3 4 5) + +;; flatten работи с tree-seq +(flatten [1 [2 [3 4]] 5]) ;; => (1 2 3 4 5) + +;; Postwalk и prewalk +(require '[clojure.walk :as walk]) +(walk/postwalk #(if (number? %) (* 2 %) %) [1 [2 3] 4]) +;; => [2 [4 6] 8] +``` + +--- + +## 8. Деструктуриране + +Деструктурирането ви позволява да свързвате локални променливи към части от колекции. + +### 8.1 Деструктуриране на вектори + +```clojure +(let [[a b c] [1 2 3]] + (+ a b c)) ;; => 6 + +;; Пропускане на елементи +(let [[a _ c] [1 2 3]] + c) ;; => 3 + +;; Останал pattern +(let [[a & rest] [1 2 3 4]] + rest) ;; => (2 3 4) + +;; Със стойности по подразбиране +(let [[a b c d] [1 2]] + [a b c d]) ;; => [1 2 nil nil] + +;; Използване на :or за подразбиране +(let [[a b :or {b 10}] [1]] + b) ;; => 10 +``` + +### 8.2 Деструктуриране на map-ове + +```clojure +(let [{a :a b :b} {:a 1 :b 2}] + (+ a b)) ;; => 3 + +;; Преименуване на ключове +(let [{x :a y :b :as original} {:a 1 :b 2}] + [x y original]) ;; => [1 2 {:a 1 :b 2}] + +;; Със стойности по подразбиране +(let [{name :name :or {name "Анонимен"}} {}] + name) ;; => "Анонимен" + +;; Използване на :keys за автоматично именуване +(let [{:keys [name age city]} {:name "John" :age 30 :city "Boston"}] + [name age city]) ;; => ["John" 30 "Boston"] + +;; Използване на :strs за ключове низове +(let [{:strs [name age]} {"name" "John" "age" 30}] + name) ;; => "John" + +;; Използване на :syms за ключове символи +(let [{:syms [x y]} {'x 1 'y 2}] + x) ;; => 1 +``` + +### 8.3 Вложено деструктуриране + +```clojure +(let [[[x y] [a b]] [[1 2] [3 4]]] + (+ x y a b)) ;; => 10 + +(let [{name :user {:keys [city state]} :address} + {:user "John" :address {:city "Boston" :state "MA"}}] + city) ;; => "Boston" +``` + +### 8.4 Деструктуриране в параметри на функции + +```clojure +(defn process [[first second & rest]] + {:first first + :second second + :rest rest}) + +(process [1 2 3 4 5]) +;; => {:first 1 :second 2 :rest (3 4 5)} + +(defn greet [{:keys [name age]}] + (str "Здравей, " name "! На " age " години си.")) + +(greet {:name "Alice" :age 25}) +;; => "Здравей, Alice! На 25 години си." +``` + +### 8.5 Деструктуриране с :as + +```clojure +(defn total [{:keys [a b c] :as numbers}] + (+ a b c)) + +(total {:a 1 :b 2 :c 3 :d 4}) ;; => 6, numbers все още има :d +``` + +--- + +## 9. Пространства от имена + +### 9.1 Създаване и превключване на пространства от имена + +```clojure +(ns myapp.core) + +(ns myapp.utils + (:require [clojure.string :as str])) + +;; В REPL +(in-ns 'myapp.core) +``` + +### 9.2 Отнасяне и импортиране + +```clojure +(ns myapp.core + (:require [clojure.string :as str] + [clojure.set :as set] + [clojure.walk :as walk]) + (:import [java.util Date UUID])) ;; Java interop, показано за пълнота +``` + +### 9.3 Често използвани директиви за namespace + +```clojure +(:require [module :as alias]) +(:require [module :refer [fn1 fn2]]) +(:require [module :refer :all]) ;; Избягвайте в production + +(:use [module]) ;; Остаряло, предпочитайте :require с :refer + +(:import [java.util Date]) ;; Java interop +``` + +### 9.4 Опции на ns макроса + +| Опция | Предназначение | +|--------|---------| +| `:require` | Зарежда модули с незадължителен alias | +| `:use` | Зарежда и отнася символи | +| `:import` | Импортира Java класове | +| `:refer-clojure` | Контролира referrals към core | +| `:load` | Зарежда произволен код | +| `:gen-class` | Генерира Java клас | + +### 9.5 Работа с пространства от имена + +```clojure +;; Създаване на var +(def x 10) + +;; Вземане на текущото namespace +*ns* ;; => #namespace[user] + +;; Resolve на символ +(resolve 'x) ;; => #'user/x + +;; Създаване на namespace +(create-ns 'myapp.data) + +;; Intern на var +(intern 'myapp.data (symbol "y") 20) + +;; Вземане на всички vars в namespace +(ns-publics 'myapp.core) +``` + +### 9.6 Добри практики за namespace + +1. Едно namespace на файл +2. Използвайте смислени имена (напр. `myapp.http.client`) +3. Използвайте последователно aliasing +4. Минимизирайте `:use`, предпочитайте `:require` с `:refer` +5. Събирайте свързани кодове заедно + +--- + +## 10. Макроси + +### 10.1 Какво са макросите? + +Макросите са код, който трансформира код преди оценяване. Те получават неоценен код и връщат нов код за оценяване. + +```clojure +;; Прост макрос +(defmacro unless [condition & body] + `(if (not ~condition) + (do ~@body))) + +;; Употреба +(unless (= 1 2) + (println "Математиката работи!") + (+ 1 2)) +``` + +### 10.2 Синтаксис цитат (Syntax Quote) + +Обратната кавичка (`) предотвратява оценяване и позволява темплейти: + +```clojure +(defmacro debug [expr] + `(let [result ~expr] + (println "Debug:" '~expr "=" result) + result)) +``` + +### 10.3 Unquoting + +- `~` (unquote) - Оценява и вмъква +- `~@` (unquote-splicing) - Оценява и разгъва последователност + +```clojure +(defmacro with-logging [expr] + `(do + (println "Изпълнява:" '~expr) + (let [result ~expr] + (println "Резултат:" result) + result))) + +;; Splicing пример +(defmacro chain [& forms] + `(do ~@forms)) + +(chain + (println "Първо") + (println "Второ")) +``` + +### 10.4 Кога да използваме макроси + +**Използвайте макроси когато:** +- Трябва да контролирате оценяването (като `if`, `when`, `unless`) +- Трябва да свързвате символи, не стойности (като `let`, `doseq`) +- Трябва да правите изчисления по време на компилация + +**Използвайте функции когато:** +- Логиката може да се изрази като трансформация на данни +- Върнатата стойност е данни, не код + +### 10.5 Разгъване на макроси + +```clojure +;; Вижте какво произвежда макрос без да го изпълнявате +(macroexpand '(when (> x 10) + (println "Голям") + (inc x))) + +;; macroexpand-1 за една стъпка +``` + +### 10.6 Често срещани модели за макроси + +#### 10.6.1 Анафорични макроси (имплицитно свързване) + +```clojure +(defmacro with-local-vars [& body] + `(let [] + ~@(map (fn [form] + `(quote ~(transform form))) + body))) + +;; По-прост: threading macros +(->> x + (filter even?) + (map inc) + (take 5)) +``` + +#### 10.6.2 Условна компилация + +```clojure +(defmacro when-bind [[sym test] & body] + `(let [~sym ~test] + (when ~sym + ~@body))) + +(when-bind [x (find-value data)] + (process x)) +``` + +### 10.7 Хигиена + +По подразбиране Clojure макросите са **хигиенични** - не изпускат нежелани свързвания. Въпреки това можете да създавате gensyms за ясен контрол: + +```clojure +(defmacro my-macro [] + (let [temp# (gensym "temp")] + `(let [~temp# 10] + ~temp#))) + +;; temp# auto-gensyms за всяка употреба +``` + +--- + +## 11. Конкурентност + +Clojure предоставя множество безопасни модели за конкурентност. Всички структури от данни в Clojure са неизменяеми, което елиминира цели класове от грешки свързани с конкурентността. + +### 11.1 Атоми (Atoms) + +Атомите предоставят синхронна, независима работа със състояние: + +```clojure +(def counter (atom 0)) + +;; Четете стойността +(deref counter) ;; => 0 +@counter ;; => 0 + +;; Обновявате с функция +(swap! counter inc) ;; => 1 +(swap! counter + 5) ;; => 6 + +;; Нулирате към стойност +(reset! counter 0) ;; => 0 + +;; Обновяване с множество аргументи +(swap! counter + 1 2 3) ;; => 6 +``` + +### 11.2 Референции (Refs) + +Референциите предоставят синхронизирано, координирано състояние чрез Software Transactional Memory (STM): + +```clojure +(def account1 (ref 100)) +(def account2 (ref 200)) + +;; dosync създава транзакция +(dosync + (alter account1 - 50) + (alter account2 + 50)) + +;; Refs могат да бъдат модифицирани само в рамките на dosync +``` + +### 11.3 Агенти (Agents) + +Агентите предоставят асинхронни, независими обновления на състояние: + +```clojure +(def logger (agent [])) + +;; Изпратете обновление (асинхронно) +(send logger conj "event-1") + +;; Изчакайте завършване +(await logger) + +;; Send-off за блокиращи операции +(send-off logger #(Thread/sleep 1000)) +``` + +### 11.4 Променливи (Vars) + +Vars предоставят thread-local и namespace-scoped състояние: + +```clojure +(def ^:dynamic *max-connections* 100) + +;; Динамично свързване +(binding [*max-connections* 50] + (*max-connections*)) ;; => 50 + +;; Thread-local +(def ^:dynamic *thread-id* nil) + +(defn get-thread-id [] + (binding [*thread-id* (java.lang.Thread/currentThread)] + *thread-id*)) +``` + +### 11.5 Futures + +Futures изпълняват код конкурентно: + +```clojure +(def my-future (future (+ 1 2 3))) + +;; Dereference за да получите резултата +@my-future ;; => 6 + +;; Проверете дали е завършило +(future-done? my-future) ;; => true + +;; Отказ (ако е възможно) +;; (future-cancel my-future) +``` + +### 11.6 Promises и Delivered + +Promises са placeholders за единична стойност: + +```clojure +(def p (promise)) + +;; Доставяте стойност +(deliver p 42) + +;; Блокирате докато бъде доставено +@promise ;; => 42 + +;; Timeout +(deref p 1000 :timeout) ;; Връща :timeout след 1000ms +``` + +### 11.7 Threads + +```clojure +;; Стартирате thread +(.start (Thread. #(println "Работи в thread"))) + +;; С повече контрол +(let [t (Thread. ^Runnable (fn [] + (println "Thread тяло")))] + (.start t)) +``` + +### 11.8 Насоки за STM + +1. Поддържайте транзакциите кратки +2. Избягвайте странични ефекти в транзакции +3. Използвайте commute за комутативни операции +4. Използвайте ref-set за прости присвоявания +5. Retry се случва автоматично при конфликт + +```clojure +;; commute за комутативни операции (редът няма значение) +(dosync + (commence total count operation)) +``` + +--- + +## 12. Протоколи и записи + +### 12.1 Протоколи + +Протоколите дефинират сигнатури на методи, които типовете могат да имплементират: + +```clojure +(defprotocol Shape + (area [this]) + (perimeter [this])) + +(defprotocol Movable + (move [this dx dy])) +``` + +### 12.2 Записи + +Записите са конкретни типове данни, които могат да имплементират протоколи: + +```clojure +(defrecord Point [x y] + Shape + (area [this] 0) + (perimeter [this] 0) + Movable + (move [this dx dy] (->Point (+ x dx) (+ y dy)))) + +;; Създаване на инстанция +(->Point 3 4) ;; => #user.Point{:x 3 :y 4} +(Point. 3 4) ;; Java-style конструктор + +;; Factory функция (автогенерирана) +(map->Point {:x 10 :y 20}) +``` + +### 12.3 Разширяване на съществуващи типове + +Разширете типове да имплементират протоколи: + +```clojure +(extend-protocol Shape + java.awt.geom.Area + (area [this] (.getBounds this)) + + nil + (area [this] 0)) + +;; extend за единични инстанции +(defmethod area :default [this] + (when (sequential? this) + (count this))) +``` + +### 12.4 Reify + +Създавате анонимни инстанции: + +```clojure +(def circle + (reify Shape + (area [this] (* Math/PI (.radius this) (.radius this))) + (perimeter [this] (* 2 Math/PI (.radius this))) + :radius 5)) + +;; Не може лесно да улови външно състояние - използвайте records за това +``` + +--- + +## 13. Многометодни функции + +Многометодните функции предоставят полиморфизъм чрез произволен dispatch: + +### 13.1 Дефиниране на многометодни функции + +```clojure +(defmulti process type) + +(defmethod process :default [x] + (str "Неизвестно: " x)) + +(defmethod process Number [x] + (inc x)) + +(defmethod process String [x] + (clojure.string/upper-case x)) +``` + +### 13.2 Функции за dispatch + +```clojure +;; Dispatch по стойност +(defmulti kind identity) + +;; Dispatch по множество стойности +(defmulti describe + (fn [x y] + [(type x) (type y)])) + +;; Dispatch по property +(defrecord User [role]) +(defmethod describe [:user :admin] [_] "Администратор") +(defmethod describe [:user :guest] [_] "Гост") +``` + +### 13.3 Йерархии + +```clojure +;; Derive създава наследяване за dispatch +(derive ::rect ::shape) +(derive ::circle ::shape) +(derive ::square ::rect) + +;; Dispatch работи с йерархията +(defmulti area :type) + +(defmethod area ::rect [r] + (* (:width r) (:height r))) + +(defmethod area ::circle [c] + (* Math/PI (:radius c) (:radius c))) +``` + +### 13.4 remove-method + +```clojure +(remove-method process String) +``` + +--- + +## 14. Тестване + +### 14.1 Clojure.test + +```clojure +(ns myapp.core-test + (:require [clojure.test :as t] + [myapp.core :as core])) + +(t/deftest addition-test + (t/testing "основно събиране" + (t/is (= 4 (+ 2 2))) + (t/is (= 5 (+ 2 2))) ;; Неуспех + (t/are [x y] (= x y) + 2 (+ 1 1) + 4 (+ 2 2)))) + +(t/deftest collection-test + (t/is (vector? [])) + (t/is (empty? [])) + (t/is (= 3 (count [1 2 3])))) +``` + +### 14.2 Fixtures + +```clojure +(defn setup [f] + (направете нещо преди) + (f) + (направете нещо след)) + +(t/use-fixtures :each setup) ;; Изпълнява за всеки тест +(t/use-fixtures :once setup) ;; Изпълнява веднъж за всички тестове +``` + +### 14.3 Пускане на тестове + +```bash +clojure -M:test +lein test +``` + +### 14.4 Генеративно тестване (test.check) + +```clojure +(require '[clojure.test.check :as tc] + '[clojure.test.check.generators :as gen] + '[clojure.test.check.properties :as prop]) + +(def sort-idempotent + (prop/for-all [v (gen/vector gen/int)] + (= (sort v) (sort (sort v))))) + +(tc/quick-check 100 sort-idempotent) +``` + +--- + +## 15. REPL + +### 15.1 Команди на REPL + +| Команда | Описание | +|---------|---------| +| `doc` | Преглед на документация | +| `find-doc` | Търсене в документите | +| `source` | Преглед на source код | +| `pst` | Отпечатване на stack trace | +| `apropos` | Търсене на символи | +| `dir` | Списък на vars в namespace | + +### 15.2 REPL работен процес + +```clojure +;; Зареждане на код +(require '[myapp.core :as core] :reload) + +;; Изчистване на REPL състояние +(remove-all-methods multimethod :default) + +;; Хващане на изключения + CompilerException ... + +;; Pretty print +(require '[clojure.pprint :as pp]) +(pp/pprint data) +``` + +### 15.3 Интеграция с редактор + +- **VS Code + Calva**: `:jack-in` за стартиране на REPL +- **Emacs + CIDER**: `cider-jack-in` +- **Vim + Conjure**: Свързва се автоматично + +--- + +## 16. Core.async + +Core.async предоставя асинхронно програмиране с канали. + +### 16.1 Канали + +```clojure +(require '[clojure.core.async :as async]) + +(def ch (async/chan)) + +;; Поставяне на стойност (блокира ако буферът е пълен) +(async/>!! ch "hello") + +;; Вземане на стойност (блокира ако е празен) +(async/ "hello" + +;; Затваряне на канал +(async/close! ch) +``` + +### 16.2 Threaded Channels + +```clojure +;; >!! и ! и ! out-ch "result")) +``` + +### 16.4 Buffers + +```clojure +(async/chan 10) ;; Фиксиран буфер +(async/chan (async/sliding-buffer 100)) ;; Пуска стари +(async/chan (async/dropping-buffer 100)) ;; Пуска нови +``` + +### 16.5 Pipeline + +```clojure +(async/pipeline-async 4 + out-ch + (fn [input ch] + (async/go + (async/>! ch (process input)))) + in-ch) +``` + +--- + +## 17. Добри практики + +### 17.1 Организация на кода + +```clojure +;; Типична структура на namespace +(ns myapp.core + (:require [myapp.util :as util] + [myapp.spec :as spec] + [clojure.string :as str]) + (:import [java.util Date])) ;; Показано само за пълнота +``` + +### 17.2 Неизменяеми данни + +Предпочитайте неизменяеми структури от данни. Когато мутация е нужна: +- Използвайте atoms за независимо състояние +- Използвайте refs със STM за координирано състояние +- Избягвайте странични ефекти в чисти функции + +### 17.3 Конвенции за именуване + +| Тип | Конвенция | Пример | +|------|------------|--------| +| Vars | kebab-case | `defn calculate-total` | +| Класове/Записи | PascalCase | `defrecord UserProfile` | +| Константи | UPPER-SNAKE | `def MAX-RETRY` | +| Private vars | trailing underscore | `defn- internal-func` | +| Dynamic vars | *заобиколени* | `def *max-connections*` | + +### 17.4 Обработка на грешки + +```clojure +(defn safe-parse + [s] + (try + (Long/parseLong s) + (catch NumberFormatException _ + nil))) + +;; С ex-info за структурирани грешки +(defn validate [x] + (when (neg? x) + (throw (ex-info "Трябва да е положително" {:value x})))) +``` + +### 17.5 Съвети за производителност + +1. Използвайте `transduce` вместо `into` + трансформация +2. Използвайте `mapv` когато се нуждаете от векторен резултат +3. Използвайте `filterv` за филтрирани вектори +4. Използвайте `reduce-kv` за итерация върху map +5. Помислете за `transducers` за ефективни трансформации + +### 17.6 Threading Macros + +Направете кода по-четим: + +```clojure +;; Thread-first (->) +(-> user + (assoc :last-login (java.time Instant/now)) + (update :login-count inc) + :last-login) + +;; Thread-last (->>) +(->> users + (map :name) + (filter #(.startsWith % "A")) + (sort) + (take 10)) + +;; Thread-as (some->, some->>) +(some-> {:user {:profile {:avatar "url"}}} + :user :profile :avatar + clojure.string/upper-case) +``` + +--- + +## 18. Индекс + +### A + +- `atom` - [11.1](#11-атоми-atoms) +- `agent` - [11.3](#113-агенти-agents) +- `and` - [3.5](#35-специални-форми) +- `are` - [14.1](#141-clojuretest) +- `apply` - [7.3.3](#733-reduce) +- `as->` - [17.6](#176-threading-macros) +- `assert` - [5.5](#55-пред--и-пост-условия) +- `assoc` - [4.9](#49-map-ове-maps) +- `async/chan` - [16.1](#161-канали) + +### B + +- `binding` - [11.4](#114-променливи-vars) +- `butlast` - [7.3.7](#737-interpose--interleave) + +### C + +- `case` - [6.1.5](#615-case) +- `comment` - [3.5](#35-коментари) +- `comp` - [5.3](#53-функции-от-по-висок-ред) +- `concat` - [4.7](#47-списъци-lists) +- `cond` - [6.1.3](#613-cond) +- `condp` - [6.1.4](#614-condp) +- `conj` - [4.8](#48-вектори-vectors) +- `cons` - [4.7](#47-списъци-lists) +- `def` - [3.3.1](#331-def) +- `defmacro` - [10.1](#101-какво-са-макросите) +- `defmethod` - [13.1](#131-дефиниране-на-многометодни-функции) +- `defmulti` - [13.1](#131-дефиниране-на-многометодни-функции) +- `defn` - [5.1.1](#511-основен-синтаксис) +- `defprotocol` - [12.1](#121-протоколи) +- `defrecord` - [12.2](#122-записи) +- `defref` - [11.2](#112-референции-refs) +- `delay` - [11.6](#116-promises-и-delivered) +- `destructure` - [8](#8-деструктуриране) +- `disj` - [4.10](#410-множества-sets) +- `dissoc` - [4.9](#49-map-ове-maps) +- `doseq` - [6.2.4](#624-doseq-странични-ефекти) +- `dosync` - [11.2](#112-референции-refs) +- `dotimes` - [6.2.3](#623-for-list-comprehension) +- `drop` - [7.3.6](#736-take--drop) +- `drop-while` - [7.3.6](#736-take--drop) + +### E + +- `empty?` - [4.12](#412-библиотека-за-колекции) +- `extend-protocol` - [12.3](#123-разширяване-на-съществуващи-типове) +- `extend-type` - [12.3](#123-разширяване-на-съществуващи-типове) + +### F + +- `fdef` - [5.5](#55-пред--и-пост-условия) +- `filter` - [7.3.2](#732-filter--remove) +- `filterv` - [7.3.2](#732-filter--remove) +- `find-doc` - [15.1](#151-команди-на-repl) +- `first` - [4.7](#47-списъци-lists) +- `flatten` - [7.3.7](#737-interpose--interleave) +- `flip` - [11.2](#112-референции-refs) +- `fn` - [5.2](#52-анонимни-функции) +- `for` - [6.2.3](#623-for-list-comprehension) +- `force` - [11.6](#116-promises-и-delivered) +- `format` - [2.3](#23-настройка-на-редактора) +- `future` - [11.5](#115-futures) + +### G + +- `gen-class` - [9.4](#94-опции-на-ns-макроса) +- `get` - [4.8](#48-вектори-vectors) +- `get-in` - [4.9](#49-map-ове-maps) +- `group-by` - [7.3.7](#737-interpose--interleave) + +### H + +- `hash-map` - [4.9](#49-map-ове-maps) +- `hash-set` - [4.10](#410-множества-sets) + +### I + +- `if` - [3.3.3](#333-if) +- `if-let` - [6.1.2](#612-when--when-not) +- `if-not` - [6.1.1](#611-if--if-not) +- `import` - [9.3](#93-отнасяне-и-импортиране) +- `inc` - [4.2](#42-низове) +- `indexed` - [7.3.7](#737-interpose--interleave) +- `into` - [7.3.7](#737-interpose--interleave) +- `interleave` - [7.3.8](#738-interpose--interleave) +- `interpose` - [7.3.8](#738-interpose--interleave) +- `iterate` - [7.2](#72-мързеливи-серии) + +### J + +- `juxt` - [5.3](#53-функции-от-по-висок-ред) + +### K + +- `keys` - [8.2](#82-деструктуриране-на-map-ове) + +### L + +- `let` - [3.3.2](#332-let) +- `letfn` - [5.1.3](#513-променлив-брой-аргументи) +- `list` - [4.7](#47-списъци-lists) +- `list*` - [4.7](#47-списъци-lists) +- `load-file` - [15.2](#152-repl-работен-процес) +- `loop` - [6.2.2](#622-looprecur) + +### M + +- `macroexpand` - [10.5](#105-разгъване-на-макроси) +- `macroexpand-1` - [10.5](#105-разгъване-на-макроси) +- `map` - [7.3.1](#731-map) +- `map-indexed` - [7.3.1](#731-map) +- `mapcat` - [7.3.5](#735-mapcat) +- `mapv` - [7.3.1](#731-map) +- `max-key` - [5.3](#53-функции-от-по-висок-ред) +- `merge` - [4.9](#49-map-ове-maps) +- `merge-with` - [4.9](#49-map-ове-maps) +- `meta` - [3.3.1](#331-def) +- `min-key` - [5.3](#53-функции-от-по-висок-ред) +- `mod` - [4.1.1](#411-целочислени-типове) + +### N + +- `namespace` - [9.5](#95-работа-с-пространства-от-имена) +- `neg?` - [4.2](#42-низове) +- `nil?` - [4.4](#44-булеви-стойности) +- `not` - [4.4](#44-булеви-стойности) +- `not-empty` - [4.12](#412-библиотека-за-колекции) +- `ns` - [9.1](#91-създаване-и-превключване-на-пространства-от-имена) +- `ns-publics` - [9.5](#95-работа-с-пространства-от-имена) +- `ns-resolve` - [9.5](#95-работа-с-пространства-от-имена) + +### O + +- `or` - [3.5](#35-специални-форми) + +### P + +- `parallelize` - [11.7](#117-насоки-за-stm) +- `partition` - [7.3.7](#737-interpose--interleave) +- `partition-all` - [7.3.7](#737-interpose--interleave) +- `partition-by` - [7.3.7](#737-interpose--interleave) +- `partial` - [5.3](#53-функции-от-по-висок-ред) +- `peek` - [4.7](#47-списъци-lists) +- `persist` - [7.2](#72-мързеливи-серии) +- `pmap` - [7.3.1](#731-map) +- `pop` - [4.8](#48-вектори-vectors) +- `pos?` - [4.2](#42-низове) +- `promise` - [11.6](#116-promises-и-delivered) + +### Q + +- `quote` - [3.3.4](#334-quote) + +### R + +- `rand` - [7.4](#74-създаване-на-серии) +- `rand-int` - [7.4](#74-създаване-на-серии) +- `range` - [7.4](#74-създаване-на-серии) +- `recur` - [6.2.1](#621-рекурсия) +- `reduce` - [7.3.3](#733-reduce) +- `reduce-kv` - [7.3.3](#733-reduce) +- `reductions` - [7.3.3](#733-reduce) +- `ref` - [11.2](#112-референции-refs) +- `ref-set` - [11.2](#112-референции-refs) +- `release-pending-sends` - [11.3](#113-агенти-agents) +- `remove` - [7.3.2](#732-filter--remove) +- `repeat` - [7.4](#74-създаване-на-серии) +- `repeatedly` - [7.4](#74-създаване-на-серии) +- `replicate` - [7.4](#74-създаване-на-серии) +- `require` - [9.3](#93-отнасяне-и-импортиране) +- `reset!` - [11.1](#11-атоми-atoms) +- `rest` - [4.7](#47-списъци-lists) +- `reverse` - [7.3.9](#739-distinct--sort--shuffle) + +### S + +- `select-keys` - [4.9](#49-map-ове-maps) +- `send` - [11.3](#113-агенти-agents) +- `send-off` - [11.3](#113-агенти-agents) +- `seq` - [7.1](#71-абстракцията-серия-sequence) +- `set` - [4.10](#410-множества-sets) +- `set!` - [11.4](#114-променливи-vars) +- `short-circuit` - [3.5](#35-специални-форми) +- `shuffle` - [7.3.9](#739-distinct--sort--shuffle) +- `shutdown-agents` - [11.3](#113-агенти-agents) +- `some` - [7.3.2](#732-filter--remove) +- `some->` - [17.6](#176-threading-macros) +- `some-fn` - [5.3](#53-функции-от-по-висок-ред) +- `sort` - [7.3.9](#739-distinct--sort--shuffle) +- `sort-by` - [7.3.9](#739-distinct--sort--shuffle) +- `split-at` - [7.3.6](#736-take--drop) +- `split-with` - [7.3.6](#736-take--drop) +- `str` - [4.2](#42-низове) +- `subs` - [4.2](#42-низове) +- `superiors` - [13.3](#133-йерархии) +- `swap!` - [11.1](#11-атоми-atoms) + +### T + +- `take` - [7.3.6](#736-take--drop) +- `take-nth` - [7.3.6](#736-take--drop) +- `take-while` - [7.3.6](#736-take--drop) +- `test` - [14.1](#141-clojuretest) +- `thread-bound?` - [11.4](#114-променливи-vars) +- `throw` - [6.3](#63-обработка-на-изключения) +- `tree-seq` - [7.5](#75-обхождане-на-колекции) +- `try` - [6.3](#63-обработка-на-изключения) +- `type` - [12.2](#122-записи) + +### U + +- `update` - [4.9](#49-map-ове-maps) +- `update-in` - [4.9](#49-map-ове-maps) +- `use` - [9.3](#93-отнасяне-и-импортиране) + +### V + +- `val` - [7.1](#71-абстракцията-серия-sequence) +- `vals` - [4.9](#49-map-ове-maps) +- `var` - [3.3.1](#331-def) +- `var-get` - [11.4](#114-променливи-vars) +- `var-set` - [11.4](#114-променливи-vars) +- `vec` - [4.8](#48-вектори-vectors) +- `vector` - [4.8](#48-вектори-vectors) +- `vector-of` - [4.8](#48-вектори-vectors) +- `volatile!` - [11.1](#11-атоми-atoms) + +### W + +- `when` - [6.1.2](#612-when--when-not) +- `when-bind` - [10.6.2](#1062-условна-компилация) +- `when-first` - [6.1.2](#612-when--when-not) +- `when-let` - [6.1.2](#612-when--when-not) +- `when-not` - [6.1.2](#612-when--when-not) +- `while` - [6.2](#62-итерация) + +### Z + +- `zero?` - [4.2](#42-низове) +- `zipmap` - [4.9](#49-map-ове-maps) + +--- + +## Приложение А: Бърза справка + +### Основни функции + +| Функция | Описание | +|----------|-------------| +| `inc` / `dec` | Инкремент / декремент | +| `+` / `-` / `*` / `/` | Аритметика | +| `=` / `==` / `not=` | Равенство | +| `<` / `>` / `<=` / `>=` | Сравнение | +| `and` / `or` / `not` | Логически | +| `first` / `rest` / `next` | Операции със серии | +| `cons` / `conj` / `concat` | Изграждане на колекции | +| `map` / `filter` / `reduce` | Трансдюсъри | +| `get` / `assoc` / `dissoc` | Операции с map | +| `get-in` / `assoc-in` | Вложени операции | +| `apply` / `partial` | Приложение на функция | +| `comp` / `juxt` / `memoize` | Комбинатори на функции | + +### Обобщение на структурите от данни + +| Тип | Литерал | Достъп | Неизменяем? | +|------|---------|--------|------------| +| Списък | `'(1 2 3)` | `first`, `nth` | Да | +| Вектор | `[1 2 3]` | `get`, `nth` | Да | +| Map | `{:a 1}` | `get`, `keys` | Да | +| Множество | `#{1 2 3}` | `get`, `contains?` | Да | + +--- + +## Приложение Б: Речник + +**Атом (Atom)** - Мутабилен контейнер, който осигурява синхронни, независими обновления. + +**Затваряне (Closure)** - Функция, която улавя и запазва достъп до променливи от обграждащия си обхват. + +**Деструктуриране (Destructuring)** - Свързване на локални променливи към части от колекция или map. + +**Хигиеничен макрос (Hygienic Macro)** - Макрос, който не изпуска нежелани свързвания. + +**Мързелива серия (Lazy Sequence)** - Серия, чиито елементи се изчисляват при поискване. + +**Протокол (Protocol)** - Именовано множество от сигнатури на методи, които типовете могат да имплементират. + +**Референция (Ref)** - Мутабилен контейнер, управляван от STM за координирани обновления. + +**S-Израз (S-Expression)** - Списък с кръгли скоби в Lisp синтаксиса. + +**STM** - Software Transactional Memory, модел за конкурентност използващ транзакции. + +**Var** - Мутабилен контейнер, който осигурява thread-local и namespace-scoped състояние. + +--- + +*Чист Clojure: Изчерпателно Ръководство* +*Версия 1.0* diff --git a/books/clojure-book/bg/02-advanced.md b/books/clojure-book/bg/02-advanced.md new file mode 100644 index 0000000..615be02 --- /dev/null +++ b/books/clojure-book/bg/02-advanced.md @@ -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: Разширени теми* diff --git a/books/clojure-book/bg/03-tooling.md b/books/clojure-book/bg/03-tooling.md new file mode 100644 index 0000000..4853c49 --- /dev/null +++ b/books/clojure-book/bg/03-tooling.md @@ -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: Структура на проекта и инструменти* diff --git a/books/clojure-book/bg/04-recipes.md b/books/clojure-book/bg/04-recipes.md new file mode 100644 index 0000000..0abe29f --- /dev/null +++ b/books/clojure-book/bg/04-recipes.md @@ -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 value)) + (recur (! out window) + (let [next-items (vec (take overlap (rest window))) + remaining (- size overlap) + new-items (vec (take remaining (async/ (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: Практически рецепти* diff --git a/books/clojure-book/bg/05-clojure-nim.md b/books/clojure-book/bg/05-clojure-nim.md new file mode 100644 index 0000000..13e90d9 --- /dev/null +++ b/books/clojure-book/bg/05-clojure-nim.md @@ -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 код. Трябва ти само добър компилатор.* diff --git a/books/clojure-book/bg/index.md b/books/clojure-book/bg/index.md new file mode 100644 index 0000000..3fd9bad --- /dev/null +++ b/books/clojure-book/bg/index.md @@ -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) diff --git a/books/clojure-book/en/01-fundamentals.md b/books/clojure-book/en/01-fundamentals.md new file mode 100644 index 0000000..8774337 --- /dev/null +++ b/books/clojure-book/en/01-fundamentals.md @@ -0,0 +1,2036 @@ +# Pure Clojure: A Comprehensive Guide + +## Table of Contents + +1. [Introduction to Clojure](#1-introduction-to-clojure) +2. [Getting Started](#2-getting-started) +3. [Basic Syntax and Forms](#3-basic-syntax-and-forms) +4. [Data Structures](#4-data-structures) +5. [Functions](#5-functions) +6. [Control Flow](#6-control-flow) +7. [Sequences and Lazy Evaluation](#7-sequences-and-lazy-evaluation) +8. [Destructuring](#8-destructuring) +9. [Namespaces](#9-namespaces) +10. [Macros](#10-macros) +11. [Concurrency](#11-concurrency) +12. [Protocols and Records](#12-protocols-and-records) +13. [Multimethods](#13-multimethods) +14. [Testing](#14-testing) +15. [The REPL](#15-the-repl) +16. [Core.async](#16-coreasync) +17. [Best Practices](#17-best-practices) +18. [Index](#18-index) + +--- + +## 1. Introduction to Clojure + +### 1.1 What is Clojure? + +Clojure is a modern, dynamic, and functional programming language that runs on the Java Virtual Machine (JVM), the .NET Common Language Runtime (CLR), and JavaScript engines via ClojureScript. Created by Rich Hickey in 2007, Clojure is a dialect of Lisp that emphasizes immutability, functional programming, andconcise expressiveness. + +### 1.2 Key Characteristics of Clojure + +#### 1.2.1 Immutability by Default + +In Clojure, all data structures are immutable by default. When you "modify" a data structure, you actually create a new version with the desired changes, while the original remains unchanged. This approach leads to safer concurrent programs and cleaner code. + +```clojure +(def original [1 2 3]) +(def modified (conj original 4)) +;; original => [1 2 3] +;; modified => [1 2 3 4] +``` + +#### 1.2.2 Functional Programming + +Clojure encourages pure functions without side effects. Functions are first-class citizens that can be passed as arguments, returned from other functions, and composed together. + +```clojure +(def double (partial * 2)) +(def add-ten (partial + 10)) +(def transform (comp double add-ten)) +(transform 5) ;; => 30 +``` + +#### 1.2.3 Lisp Heritage + +As a Lisp dialect, Clojure inherits the power of macros and the uniform representation of code as data. Everything is an expression that returns a value, and the syntax is simple and consistent. + +#### 1.2.4 Runtime Polymorphism + +Clojure provides multiple mechanisms for polymorphism: +- **Protocols**: Define method signatures for data types +- **Multimethods**: Dispatch based on arbitrary criteria +- **Records**: Concrete data types implementing protocols + +### 1.3 Why Pure Clojure? + +While Clojure runs on the JVM and has excellent Java interop, this book focuses on **pure Clojure**—the core language features that don't rely on Java integration. This approach: + +- Teaches the fundamental concepts of Clojure +- Makes code portable (including ClojureScript) +- Encourages thinking in Clojure's paradigm +- Avoids mixing paradigms unnecessarily + +### 1.4 The Clojure Philosophy + +Clojure adheres to several principles: + +1. **Simplicity**: Hard things should be simple, simple things should be trivial +2. **Immutability**: Prefer immutable data structures for safety and concurrency +3. **Abstraction**: Build layers of abstraction to manage complexity +4. **Expression-Oriented**: Everything is an expression that returns a value + +--- + +## 2. Getting Started + +### 2.1 Installation + +#### 2.1.1 Using CLI Tools + +The recommended way to install Clojure is through the official CLI tool: + +**Linux/macOS:** +```bash +curl -O https://download.clojure.org/install/linux-install-1.11.1.1202.sh +chmod +x linux-install-1.11.1.1202.sh +./linux-install-1.11.1.1202.sh +``` + +**macOS (Homebrew):** +```bash +brew install clojure +``` + +#### 2.1.2 Manual Installation + +Download the Clojure JAR file and use it directly: +```bash +java -jar clojure-1.11.1.jar +``` + +### 2.2 Your First Clojure Project + +#### 2.2.1 Creating a Project with deps.edn + +Create a new directory for your project and add a `deps.edn` file: + +```clojure +{:deps {org.clojure/clojure {:mvn/version "1.11.1"}}} +``` + +Run Clojure: +```bash +clj -M +``` + +#### 2.2.2 REPL Basics + +The REPL (Read-Eval-Print Loop) is your primary development environment: + +```clojure +user=> (+ 1 2 3) +6 +user=> (println "Hello, Clojure!") +Hello, Clojure! +nil +user=> (def message "Hello, World!") +#'user/message +user=> message +"Hello, World!" +``` + +### 2.3 Editor Setup + +Popular editors for Clojure development: + +- **VS Code**: Calva extension +- **Emacs**: CIDER mode +- **Vim/Neovim**: Conjure plugin +- **IntelliJ**: Cursive plugin + +--- + +## 3. Basic Syntax and Forms + +### 3.1 S-Expressions + +Clojure code is written as **s-expressions** (symbolic expressions), which are nested lists: + +```clojure +(operator operands...) +``` + +The first element is the operator (function, macro, or special form), and the rest are operands. + +```clojure +(+ 1 2) ;; Addition: 3 +(* 2 3 4) ;; Multiplication: 24 +(< 1 2 3) ;; Comparison: true +(and true false) ;; Logical AND: false +``` + +### 3.2 Data as Code + +In Lisp, data and code are the same. This means you can manipulate your programs as data: + +```clojure +'(+ 1 2 3) ;; A quoted list: (+ 1 2 3) +(list + 1 2) ;; A list containing the + function and numbers +``` + +### 3.3 Special Forms + +Special forms are primitives that can't be expressed as functions because they have special evaluation rules. + +#### 3.3.1 def + +Define a global var: + +```clojure +(def x 10) +(def name "Clojure") +(def items [1 2 3]) +``` + +#### 3.3.2 let + +Create local bindings: + +```clojure +(let [x 10 + y 20] + (+ x y)) ;; => 30 +``` + +#### 3.3.3 if + +Conditional execution: + +```clojure +(if condition + then-expr + else-expr) +``` + +#### 3.3.4 quote + +Prevent evaluation: + +```clojure +(quote (+ 1 2)) ;; => (+ 1 2) +'(+ 1 2) ;; Short form: (+ 1 2) +``` + +### 3.4 Evaluation Rules + +1. Numbers, strings, booleans, nil, and keywords **evaluate to themselves** +2. Symbols **evaluate to the value** of the var they name +3. Lists **evaluate as function calls** (if first element is callable) +4. Quoted expressions **prevent evaluation** + +### 3.5 Comments + +```clojure +;; Single line comment + +;; Multi-line comment +;; (there is no special multi-line syntax, +;; just use multiple single-line comments) + +(comment + "This is a comment block that won't be evaluated" + (+ 1 2)) +``` + +### 3.6 Whitespace and Formatting + +- Clojure is whitespace-insensitive (except within symbols) +- Standard convention: one space after opening paren, before closing +- Align arguments vertically for readability: + +```clojure +(do-something arg1 + arg2 + arg3) +``` + +--- + +## 4. Data Structures + +Clojure provides a rich set of immutable data structures. Understanding them is fundamental to writing idiomatic Clojure. + +### 4.1 Numbers + +#### 4.1.1 Integer Types + +```clojure +42 ;; Decimal +017 ;; Octal (15) +0x2A ;; Hexadecimal (42) +2r101010 ;; Binary (42) +``` + +#### 4.1.2 Floating Point + +```clojure +3.14 +6.022e23 +``` + +#### 4.1.3 Ratios + +Clojure preserves precision with ratios: + +```clojure +1/3 ;; Ratio type +22/7 ;; Approximation of pi +(/ 1 3) ;; 1/3 +``` + +### 4.2 Strings + +```clojure +"Hello, World!" +"Multi-line +string" + +;; Concatenation +(str "Hello" " " "World") ;; => "Hello World" + +;; Substring +(subs "Hello" 0 5) ;; => "Hello" + +;; String functions +(count "Clojure") ;; => 7 +(reverse "Clojure") ;; => "erujolC" +``` + +### 4.3 Characters + +```clojure +\a ;; Character a +\newline ;; Newline +\space ;; Space +``` + +### 4.4 Booleans + +```clojure +true +false +nil ;; Represents absence of value +``` + +Truthiness rules: +- Everything except `false` and `nil` is truthy +- `and`, `or`, `if`, `when` use this rule + +### 4.5 Keywords + +Keywords are interned strings used as identifiers, often for keys in maps: + +```clojure +:foo +:bar +:user/name ;; Namespaced keyword +::local-key ;; Auto-namespaced +``` + +Keywords evaluate to themselves and can be used as functions to look up values in maps. + +### 4.6 Symbols + +Symbols evaluate to the vars they name: + +```clojure +'x ;; Symbol x (quoted) +(def x 10) ;; Defines var x with value 10 +x ;; Evaluates to 10 +``` + +### 4.7 Lists + +Lists are linked lists, efficient for sequential access at the front: + +```clojure +'(1 2 3) ;; Quote to prevent evaluation +(list 1 2 3) ;; Create a list +'(+ 1 2) ;; A list containing the + symbol + +;; Access +(first '(1 2 3)) ;; => 1 +(second '(1 2 3)) ;; => 2 +(rest '(1 2 3)) ;; => (2 3) +(nth '(1 2 3) 0) ;; => 1 + +;; Modification (returns new list) +(cons 0 '(1 2 3)) ;; => (0 1 2 3) +(concat '(1 2) '(3 4)) ;; => (1 2 3 4) +``` + +### 4.8 Vectors + +Vectors are indexed collections, efficient for random access: + +```clojure +[1 2 3 4 5] +(vector 1 2 3) ;; => [1 2 3] + +;; Access +(get [10 20 30] 1) ;; => 20 +([10 20 30] 1) ;; => 20 (keyword-like access) +(first [1 2 3]) ;; => 1 +(second [1 2 3]) ;; => 2 +(last [1 2 3]) ;; => 3 + +;; Modification (returns new vector) +(conj [1 2] 3) ;; => [1 2 3] +(pop [1 2 3]) ;; => [1 2] +(assoc [1 2 3] 1 20) ;; => [1 20 3] +(subvec [1 2 3 4 5] 1 3) ;; => [2 3] +``` + +### 4.9 Maps + +Maps are key-value associative structures: + +```clojure +{:name "Alice" :age 30} +(hash-map :a 1 :b 2 :c 3) +(assoc {:a 1} :b 2) ;; => {:a 1 :b 2} +(dissoc {:a 1 :b 2} :a) ;; => {:b 2} +(get {:a 1} :a) ;; => 1 +({:a 1} :a) ;; => 1 +(:a {:a 1}) ;; => 1 (keywords are functions!) + +;; Nested access +(get-in {:user {:address {:city "Sofia"}}} + [:user :address :city]) ;; => "Sofia" + +;; Merging +(merge {:a 1} {:b 2} {:c 3}) ;; => {:a 1 :b 2 :c 3} +``` + +### 4.10 Sets + +Sets are collections of unique values: + +```clojure +#{1 2 3} +(hash-set 1 2 3 2 1) ;; => #{1 2 3} +(set [1 2 2 3 3 3]) ;; => #{1 2 3} + +;; Operations +(conj #{1 2} 3) ;; => #{1 2 3} +(disj #{1 2 3} 2) ;; => #{1 3} +(contains? #{1 2 3} 2) ;; => true +(get #{1 2 3} 2) ;; => 2 +(clojure.set/union #{1 2} #{2 3}) ;; => #{1 2 3} +(clojure.set/intersection #{1 2 3} #{2 3 4}) ;; => #{2 3} +(clojure.set/difference #{1 2 3} #{2 3}) ;; => #{1} +``` + +### 4.11 Structuring Data + +```clojure +;; Representing a user with a map +(def user {:name "John" + :email "john@example.com" + :roles [:admin :user]}) + +;; Nested data +(def company {:name "TechCorp" + :employees [{:name "Alice" :dept "Engineering"} + {:name "Bob" :dept "Sales"}] + :locations {:HQ "New York" + :branch "Boston"}}) +``` + +### 4.12 Collections Library + +Core collection functions that work uniformly across data structures: + +```clojure +;; Predicates +(empty? []) ;; => true +(empty? [1 2 3]) ;; => false +(every? even? [2 4 6]) ;; => true +(some odd? [2 4 5 6]) ;; => true +(not-empty [1 2 3]) ;; => [1 2 3] +(not-empty []) ;; => nil + +;; Counting +(count [1 2 3]) ;; => 3 +(count {:a 1 :b 2}) ;; => 2 + +;; Conversion +(vec '(1 2 3)) ;; => [1 2 3] +(list [1 2 3]) ;; => (1 2 3) +(set [1 2 2 3]) ;; => #{1 2 3} +(mapv inc [1 2 3]) ;; => [2 3 4] +``` + +--- + +## 5. Functions + +### 5.1 Defining Functions + +#### 5.1.1 Basic Syntax + +```clojure +(defn greeting + "Returns a greeting message" + [name] + (str "Hello, " name "!")) + +(greeting "World") ;; => "Hello, World!" +``` + +#### 5.1.2 Multiple Arities + +Functions can have different argument counts: + +```clojure +(defn add + ([x] (add x 0)) + ([x y] (+ x y)) + ([x y z] (+ x y z))) + +(add 5) ;; => 5 +(add 5 3) ;; => 8 +(add 1 2 3) ;; => 6 +``` + +#### 5.1.3 Variable Arguments + +Use `&` for rest parameters: + +```clojure +(defn sum [& numbers] + (reduce + numbers)) + +(sum 1 2 3 4 5) ;; => 15 +``` + +### 5.2 Anonymous Functions + +```clojure +(fn [x] (* x x)) +#(* % %) ;; Implicit argument +#(* %1 %2) ;; Multiple arguments +#(reduce + %&) ;; Rest arguments +``` + +### 5.3 Higher-Order Functions + +Functions that take or return other functions: + +```clojure +(def double #( % 2)) +(def square #(* % %)) + +(map double [1 2 3 4]) ;; => (2 4 6 8) +(map square [1 2 3 4]) ;; => (1 4 9 16) + +(filter even? [1 2 3 4 5 6]) ;; => (2 4 6) + +(reduce + [1 2 3 4 5]) ;; => 15 +(reduce max [3 1 4 1 5]) ;; => 5 + +;; Function composition +(def transform (comp square double)) +(transform 3) ;; => 36 (3*2=6, 6*6=36) +``` + +### 5.4 Closures + +Functions that capture their environment: + +```clojure +(defn make-adder [x] + (fn [y] (+ x y))) + +(def add-5 (make-adder 5)) +(add-5 10) ;; => 15 +(add-5 3) ;; => 8 + +;; Counter example +(defn make-counter [] + (let [count (atom 0)] + {:increment #(swap! count inc) + :decrement #(swap! count dec) + :value #(deref count)})) +``` + +### 5.5 Pre- and Post-Conditions + +```clojure +(defn absolute-value [n] + {:pre [(number? n)] + :post [(>= % 0)]} + (if (neg? n) + (- n) + n)) +``` + +### 5.6 Multimethods via defn + +While true multimethods use `defmulti` and `defmethod`, regular functions can simulate behavior-based dispatch: + +```clojure +(defn process [x] + (cond + (string? x) (clojure.string/upper-case x) + (number? x) (inc x) + :else "unknown")) +``` + +--- + +## 6. Control Flow + +### 6.1 Branching + +#### 6.1.1 if / if-not + +```clojure +(if condition + then-expr + else-expr) + +(if (pos? -5) + "positive" + "not positive") ;; => "not positive" + +;; if-not is simply (if (not condition)...) +(if-not (even? 4) + "odd" + "even") ;; => "even" +``` + +#### 6.1.2 when / when-not + +Single branch without else: + +```clojure +(when (pos? 5) + (println "Positive!") + (inc 5)) ;; => 6 + +(when-not (neg? 3) + "non-negative") ;; => "non-negative" +``` + +#### 6.1.3 cond + +Multiple conditions: + +```clojure +(defn classify [n] + (cond + (neg? n) "negative" + (zero? n) "zero" + (even? n) "positive even" + :else "positive odd")) + +(classify -5) ;; => "negative" +(classify 0) ;; => "zero" +(classify 4) ;; => "positive even" +(classify 7) ;; => "positive odd" +``` + +#### 6.1.4 condp + +Predicate-based dispatch: + +```clojure +(defn respond [msg] + (condp = msg + "hello" "Hi!" + "bye" "Goodbye!" + "how are you?" "Fine!" + "Unknown message")) + +(respond "hello") ;; => "Hi!" +``` + +#### 6.1.5 case + +Constant-time dispatch (uses hash comparison): + +```clojure +(defn http-status [code] + (case code + 200 "OK" + 301 "Moved Permanently" + 404 "Not Found" + 500 "Internal Server Error" + "Unknown")) + +(http-status 200) ;; => "OK" +(http-status 999) ;; => "Unknown" +``` + +### 6.2 Iteration + +#### 6.2.1 Recursion + +```clojure +(defn factorial [n] + (if (<= n 1) + 1 + (* n (factorial (dec n))))) + +;; With recur (tail-optimized) +(defn factorial [n] + (letfn [(fac [n acc] + (if (<= n 1) + acc + (recur (dec n) (* acc n))))] + (fac n 1))) +``` + +#### 6.2.2 loop/recur + +Explicit tail-recursive loop: + +```clojure +(loop [i 0 + result []] + (if (= i 10) + result + (recur (inc i) (conj result i)))) + +;; => [0 1 2 3 4 5 6 7 8 9] +``` + +#### 6.2.3 for (list comprehension) + +```clojure +(for [x (range 5) + :let [y (* x x)] + :when (even? y)] + y) +;; => (0 4 16) + +(for [x [:a :b :c] + y [1 2]] + [x y]) +;; => ([a 1] [a 2] [b 1] [b 2] [c 1] [c 2]) +``` + +#### 6.2.4 doseq (side effects) + +```clojure +(doseq [x (range 3) + y (range 3)] + (println [x y])) +;; Prints: +;; [0 0] +;; [0 1] +;; [0 2] +;; ... +``` + +### 6.3 Exception Handling + +```clojure +(try + (/ 1 0) + (catch ArithmeticException e + (str "Error: " (.getMessage e))) + (finally + (println "Cleanup"))) + +;; With throw +(try + (throw (ex-info "Custom error" {:code 123})) + (catch Exception e + (ex-data e))) ;; => {:code 123} +``` + +### 6.4 do + +Execute multiple expressions, return last: + +```clojure +(do + (println "Side effect") + (println "Another") + (+ 1 2)) ;; => 3 +``` + +--- + +## 7. Sequences and Lazy Evaluation + +### 7.1 The Sequence Abstraction + +Clojure provides a uniform interface for sequential collections. The key functions are: +- `first` - First element +- `rest` - All elements after first +- `cons` - Prepend element + +```clojure +;; Works on lists, vectors, strings, maps, sets, etc. +(first [1 2 3]) ;; => 1 +(rest [1 2 3]) ;; => (2 3) +(cons 0 [1 2 3]) ;; => (0 1 2 3) + +(first "hello") ;; => \h +(rest "hello") ;; => (\e \l \l \o) +(first {:a 1 :b 2}) ;; => [:a 1] +``` + +### 7.2 Lazy Sequences + +Lazy sequences are computed on-demand, allowing for: +- Infinite sequences +- Memory efficiency +- Performance optimization + +```clojure +;; range produces infinite lazy sequence +(take 10 (range)) ;; => (0 1 2 3 4 5 6 7 8 9) + +;; Fibonacci sequence +(def fibs + (lazy-cat [0 1] (map + fibs (rest fibs)))) + +(take 10 fibs) ;; => (0 1 1 2 3 5 8 13 21 34) + +;; iterate +(take 5 (iterate inc 0)) ;; => (0 1 2 3 4) +(take 5 (iterate #(* 2 %) 1)) ;; => (1 2 4 8 16) +``` + +### 7.3 Sequence Functions + +#### 7.3.1 map + +Transform each element: + +```clojure +(map inc [1 2 3]) ;; => (2 3 4) +(map + [1 2 3] [4 5 6]) ;; => (5 7 9) +(map str "abc") ;; => ("a" "b" "c") +``` + +#### 7.3.2 filter / remove + +Select/reject elements: + +```clojure +(filter even? (range 10)) ;; => (0 2 4 6 8) +(remove even? (range 10)) ;; => (1 3 5 7 9) +(filterv even? (range 10)) ;; => [0 2 4 6 8] (vector) +``` + +#### 7.3.3 reduce + +Process elements with accumulation: + +```clojure +(reduce + [1 2 3 4 5]) ;; => 15 +(reduce + 10 [1 2 3]) ;; => 16 (with initial value) +(reduce (fn [[sum cnt] x] + [(+ sum x) (inc cnt)]) + [0 0] + [1 2 3 4 5]) +;; => [15 5] +``` + +#### 7.3.4 fold + +Parallel reduction (uses reducers): + +```clojure +(require '[clojure.core.reducers :as r]) +(r/fold + (range 1000)) +``` + +#### 7.3.5 mapcat + +Map then flatten: + +```clojure +(mapcat reverse [[1 2] [3 4] [5 6]]) ;; => (2 1 4 3 6 5) +``` + +#### 7.3.6 take / drop + +```clojure +(take 3 (range 10)) ;; => (0 1 2) +(drop 3 (range 10)) ;; => (3 4 5 6 7 8 9) +(take-while pos? [3 2 1 0 -1]) ;; => (3 2 1) +(drop-while pos? [3 2 1 0 -1]) ;; => (0 -1) +(split-at 3 (range 5)) ;; => [(0 1 2) (3 4)] +``` + +#### 7.3.7 flatten / partition + +```clojure +(flatten [[1 2] [3 [4 5]]]) ;; => (1 2 3 4 5) +(partition 2 (range 8)) ;; => ((0 1) (2 3) (4 5) (6 7)) +(partition-all 3 (range 10)) ;; => ((0 1 2) (3 4 5) (6 7 8) (9)) +(partition-by even? [1 2 3 4 5 6]) ;; => ((1) (2 3 4) (5 6)) +``` + +#### 7.3.8 Interpose / Interleave + +```clojure +(interpose "," ["a" "b" "c"]) ;; => ("a" "," "b" "," "c") +(apply str (interpose "," ["a" "b" "c"])) ;; => "a,b,c" +(interleave [1 2 3] [:a :b :c]) ;; => (1 :a 2 :b 3 :c) +``` + +#### 7.3.9 distinct / sort / shuffle + +```clojure +(distinct [1 2 2 3 3 3 4]) ;; => (1 2 3 4) +(sort [3 1 4 1 5 9 2]) ;; => (1 1 2 3 4 5 9) +(sort-by :age [{:age 30} {:age 20} {:age 40}]) +;; => ({:age 20} {:age 30} {:age 40}) +(shuffle (range 5)) ;; => random order +``` + +### 7.4 Creating Sequences + +```clojure +(range) ;; Infinite 0, 1, 2, ... +(range 5) ;; (0 1 2 3 4) +(range 1 10 2) ;; (1 3 5 7 9) start, end, step +(repeat 5 :x) ;; (:x :x :x :x :x) +(repeatedly 5 #(rand-int 100)) ;; Random values +(cycle [:a :b]) ;; Infinite (:a :b :a :b ...) +``` + +### 7.5 Walking Collections + +```clojure +;; tree-seq: walk a nested structure +(tree-seq sequential? seq [1 [2 [3 4]] 5]) +;; => ([1 [2 [3 4]] 5] 1 [2 [3 4]] 2 [3 4] 3 4 5) + +;; flatten works on tree-seq +(flatten [1 [2 [3 4]] 5]) ;; => (1 2 3 4 5) + +;; Postwalk and prewalk +(require '[clojure.walk :as walk]) +(walk/postwalk #(if (number? %) (* 2 %) %) [1 [2 3] 4]) +;; => [2 [4 6] 8] +``` + +--- + +## 8. Destructuring + +Destructuring allows you to bind local variables to parts of collections. + +### 8.1 Vector Destructuring + +```clojure +(let [[a b c] [1 2 3]] + (+ a b c)) ;; => 6 + +;; Skip elements +(let [[a _ c] [1 2 3]] + c) ;; => 3 + +;; Rest pattern +(let [[a & rest] [1 2 3 4]] + rest) ;; => (2 3 4) + +;; With default values +(let [[a b c d] [1 2]] + [a b c d]) ;; => [1 2 nil nil] + +;; Using :or for defaults +(let [[a b :or {b 10}] [1]] + b) ;; => 10 +``` + +### 8.2 Map Destructuring + +```clojure +(let [{a :a b :b} {:a 1 :b 2}] + (+ a b)) ;; => 3 + +;; Rename keys +(let [{x :a y :b :as original} {:a 1 :b 2}] + [x y original]) ;; => [1 2 {:a 1 :b 2}] + +;; With defaults +(let [{name :name :or {name "Anonymous"}} {}] + name) ;; => "Anonymous" + +;; Using :keys for automatic naming +(let [{:keys [name age city]} {:name "John" :age 30 :city "Boston"}] + [name age city]) ;; => ["John" 30 "Boston"] + +;; Using :strs for string keys +(let [{:strs [name age]} {"name" "John" "age" 30}] + name) ;; => "John" + +;; Using :syms for symbol keys +(let [{:syms [x y]} {'x 1 'y 2}] + x) ;; => 1 +``` + +### 8.3 Nested Destructuring + +```clojure +(let [[[x y] [a b]] [[1 2] [3 4]]] + (+ x y a b)) ;; => 10 + +(let [{name :user {:keys [city state]} :address} + {:user "John" :address {:city "Boston" :state "MA"}}] + city) ;; => "Boston" +``` + +### 8.4 Destructuring in Function Parameters + +```clojure +(defn process [[first second & rest]] + {:first first + :second second + :rest rest}) + +(process [1 2 3 4 5]) +;; => {:first 1 :second 2 :rest (3 4 5)} + +(defn greet [{:keys [name age]}] + (str "Hello, " name "! You are " age ".")) + +(greet {:name "Alice" :age 25}) +;; => "Hello, Alice! You are 25." +``` + +### 8.5 Destructuring with :as + +```clojure +(defn total [{:keys [a b c] :as numbers}] + (+ a b c)) + +(total {:a 1 :b 2 :c 3 :d 4}) ;; => 6, numbers still has :d +``` + +--- + +## 9. Namespaces + +### 9.1 Creating and Switching Namespaces + +```clojure +(ns myapp.core) + +(ns myapp.utils + (:require [clojure.string :as str])) + +;; In the REPL +(in-ns 'myapp.core) +``` + +### 9.2 Referring and Importing + +```clojure +(ns myapp.core + (:require [clojure.string :as str] + [clojure.set :as set] + [clojure.walk :as walk]) + (:import [java.util Date UUID])) ;; Java interop, shown for completeness +``` + +### 9.3 Common Namespace Directives + +```clojure +(:require [module :as alias]) +(:require [module :refer [fn1 fn2]]) +(:require [module :refer :all]) ;; Avoid in production + +(:use [module]) ;; Deprecated, prefer :require + +(:import [java.util Date]) ;; Java interop +``` + +### 9.4 ns Macro Options + +| Option | Purpose | +|--------|---------| +| `:require` | Load modules with optional alias | +| `:use` | Load and refer symbols | +| `:import` | Import Java classes | +| `:refer-clojure` | Control core referrals | +| `:load` | Load arbitrary code | +| `:gen-class` | Generate Java class | + +### 9.5 Working with Namespaces + +```clojure +;; Create a var +(def x 10) + +;; Get current namespace +*ns* ;; => #namespace[user] + +;; Resolve a symbol +(resolve 'x) ;; => #'user/x + +;; Create namespace +(create-ns 'myapp.data) + +;; Intern a var +(intern 'myapp.data (symbol "y") 20) + +;; Get all vars in namespace +(ns-publics 'myapp.core) +``` + +### 9.6 Namespace Best Practices + +1. One namespace per file +2. Use meaningful namespace names (e.g., `myapp.http.client`) +3. Use consistent aliasing +4. Minimize `:use`, prefer `:require` with `:refer` +5. Keep related code together + +--- + +## 10. Macros + +### 10.1 What are Macros? + +Macros are code that transforms code before evaluation. They receive unevaluated code and return new code to be evaluated. + +```clojure +;; A simple macro +(defmacro unless [condition & body] + `(if (not ~condition) + (do ~@body))) + +;; Usage +(unless (= 1 2) + (println "Math works!") + (+ 1 2)) +``` + +### 10.2 Syntax Quote + +The backtick (`) prevents evaluation and allows templating: + +```clojure +(defmacro debug [expr] + `(let [result ~expr] + (println "Debug:" '~expr "=" result) + result)) +``` + +### 10.3 Unquoting + +- `~` (unquote) - Evaluate and insert +- `~@` (unquote-splicing) - Evaluate and splice sequence + +```clojure +(defmacro with-logging [expr] + `(do + (println "Executing:" '~expr) + (let [result ~expr] + (println "Result:" result) + result))) + +;; Splicing example +(defmacro chain [& forms] + `(do ~@forms)) + +(chain + (println "First") + (println "Second")) +``` + +### 10.4 When to Use Macros + +**Use macros when:** +- You need to control evaluation (like `if`, `when`, `unless`) +- You need to bind symbols, not values (like `let`, `doseq`) +- You need to do compile-time computation + +**Use functions when:** +- The logic can be expressed as data transformation +- The return value is data, not code + +### 10.5 Macro Expansion + +```clojure +;; See what a macro produces without running it +(macroexpand '(when (> x 10) + (println "Big") + (inc x))) + +;; macroexpand-1 for single step +``` + +### 10.6 Common Macro Patterns + +#### 10.6.1 Anaphoric Macros (Implicit Binding) + +```clojure +(defmacro with-local-vars [& body] + `(let [] + ~@(map (fn [form] + `(quote ~(transform form))) + body))) + +;; Simpler: threading macros +(->> x + (filter even?) + (map inc) + (take 5)) +``` + +#### 10.6.2 Conditional Compilation + +```clojure +(defmacro when-bind [[sym test] & body] + `(let [~sym ~test] + (when ~sym + ~@body))) + +(when-bind [x (find-value data)] + (process x)) +``` + +### 10.7 Hygiene + +By default, Clojure macros are **hygienic** - they don't leak bindings. However, you can create gensyms for explicit control: + +```clojure +(defmacro my-macro [] + (let [temp# (gensym "temp")] + `(let [~temp# 10] + ~temp#))) + +;; temp# auto-gensyms for each use +``` + +--- + +## 11. Concurrency + +Clojure provides multiple safe concurrency models. All Clojure data structures are immutable, eliminating entire classes of concurrency bugs. + +### 11.1 Atoms + +Atoms provide synchronous, independent state management: + +```clojure +(def counter (atom 0)) + +;; Read the value +(deref counter) ;; => 0 +@counter ;; => 0 + +;; Update with a function +(swap! counter inc) ;; => 1 +(swap! counter + 5) ;; => 6 + +;; Reset to a value +(reset! counter 0) ;; => 0 + +;; Update with multiple arguments +(swap! counter + 1 2 3) ;; => 6 +``` + +### 11.2 Refs + +Refs provide synchronized, coordinated state through Software Transactional Memory (STM): + +```clojure +(def account1 (ref 100)) +(def account2 (ref 200)) + +;;dosync creates a transaction +(dosync + (alter account1 - 50) + (alter account2 + 50)) + +;; Refs can only be modified within dosync +``` + +### 11.3 Agents + +Agents provide asynchronous, independent state updates: + +```clojure +(def logger (agent [])) + +;; Send update (async) +(send logger conj "event-1") + +;; Await completion +(await logger) + +;; Send-off for blocking operations +(send-off logger #(Thread/sleep 1000)) +``` + +### 11.4 Vars + +Vars provide thread-local and namespace-scoped state: + +```clojure +(def ^:dynamic *max-connections* 100) + +;; Bind dynamically +(binding [*max-connections* 50] + (*max-connections*)) ;; => 50 + +;; Thread-local +(def ^:dynamic *thread-id* nil) + +(defn get-thread-id [] + (binding [*thread-id* (java.lang.Thread/currentThread)] + *thread-id*)) +``` + +### 11.5 Futures + +Futures execute code concurrently: + +```clojure +(def my-future (future (+ 1 2 3))) + +@dereference the future to get the result +@my-future ;; => 6 + +;; Check if complete +(future-done? my-future) ;; => true + +;; Cancel (if possible) +;; (future-cancel my-future) +``` + +### 11.6 Promises and Delivered + +Promises are placeholders for a single value: + +```clojure +(def p (promise)) + +;; Deliver a value +(deliver p 42) + +;; Block until delivered +@dereference p ;; => 42 + +;; Timeout +(deref p 1000 :timeout) ;; Returns :timeout after 1000ms +``` + +### 11.7 Threads + +```clojure +;; Start a thread +(.start (Thread. #(println "Running in thread"))) + +;; With more control +(let [t (Thread. ^Runnable (fn [] + (println "Thread body")))] + (.start t)) +``` + +### 11.8 STM Guidelines + +1. Keep transactions short +2. Avoid side effects in transactions +3. Use commute for commutative operations +4. Use ref-set for simple assignments +5. Retry happens automatically on conflict + +```clojure +;; commute for commutative operations (order doesn't matter) +(dosync + (commence total count operation)) +``` + +--- + +## 12. Protocols and Records + +### 12.1 Protocols + +Protocols define method signatures that types can implement: + +```clojure +(defprotocol Shape + (area [this]) + (perimeter [this])) + +(defprotocol Movable + (move [this dx dy])) +``` + +### 12.2 Records + +Records are concrete data types that can implement protocols: + +```clojure +(defrecord Point [x y] + Shape + (area [this] 0) + (perimeter [this] 0) + Movable + (move [this dx dy] (->Point (+ x dx) (+ y dy)))) + +;; Create instance +(->Point 3 4) ;; => #user.Point{:x 3 :y 4} +(Point. 3 4) ;; Java-style constructor + +;; Factory function (auto-generated) +(map->Point {:x 10 :y 20}) +``` + +### 12.3 Extending Existing Types + +Extend types to implement protocols: + +```clojure +(extend-protocol Shape + java.awt.geom.Area + (area [this] (.getBounds this)) + + nil + (area [this] 0)) + +;; extend for single instances +(defmethod area :default [this] + (when (sequential? this) + (count this))) +``` + +### 12.4 Reify + +Create anonymous instances: + +```clojure +(def circle + (reify Shape + (area [this] (* Math/PI (.radius this) (.radius this))) + (perimeter [this] (* 2 Math/PI (.radius this))) + :radius 5)) + +;; Can't capture external state easily - use records for that +``` + +--- + +## 13. Multimethods + +Multimethods provide polymorphism through arbitrary dispatch: + +### 13.1 Defining Multimethods + +```clojure +(defmulti process type) + +(defmethod process :default [x] + (str "Unknown: " x)) + +(defmethod process Number [x] + (inc x)) + +(defmethod process String [x] + (clojure.string/upper-case x)) +``` + +### 13.2 Dispatch Functions + +```clojure +;; Dispatch on value +(defmulti kind identity) + +;; Dispatch on multiple values +(defmulti describe + (fn [x y] + [(type x) (type y)])) + +;; Dispatch on property +(defrecord User [role]) +(defmethod describe [:user :admin] [_] "Administrator") +(defmethod describe [:user :guest] [_] "Guest") +``` + +### 13.3 Hierarchies + +```clojure +;; Derive creates inheritance for dispatch +(derive ::rect ::shape) +(derive ::circle ::shape) +(derive ::square ::rect) + +;; Dispatch works with hierarchy +(defmulti area :type) + +(defmethod area ::rect [r] + (* (:width r) (:height r))) + +(defmethod area ::circle [c] + (* Math/PI (:radius c) (:radius c))) +``` + +### 13.4 remove-method + +```clojure +(remove-method process String) +``` + +--- + +## 14. Testing + +### 14.1 Clojure.test + +```clojure +(ns myapp.core-test + (:require [clojure.test :as t] + [myapp.core :as core])) + +(t/deftest addition-test + (t/testing "basic addition" + (t/is (= 4 (+ 2 2))) + (t/is (= 5 (+ 2 2))) ;; Fails + (t/are [x y] (= x y) + 2 (+ 1 1) + 4 (+ 2 2)))) + +(t/deftest collection-test + (t/is (vector? [])) + (t/is (empty? [])) + (t/is (= 3 (count [1 2 3])))) +``` + +### 14.2 Fixtures + +```clojure +(defn setup [f] + (do something before) + (f) + (do something after)) + +(t/use-fixtures :each setup) ;; Run for each test +(t/use-fixtures :once setup) ;; Run once for all tests +``` + +### 14.3 Running Tests + +```bash +clojure -M:test +lein test +``` + +### 14.4 Generative Testing (test.check) + +```clojure +(require '[clojure.test.check :as tc] + '[clojure.test.check.generators :as gen] + '[clojure.test.check.properties :as prop]) + +(def sort-idempotent + (prop/for-all [v (gen/vector gen/int)] + (= (sort v) (sort (sort v))))) + +(tc/quick-check 100 sort-idempotent) +``` + +--- + +## 15. The REPL + +### 15.1 REPL Commands + +| Command | Description | +|---------|-------------| +| `doc` | View documentation | +| `find-doc` | Search docs | +| `source` | View source code | +| `pst` | Print stack trace | +| `apropos` | Search symbols | +| `dir` | List vars in namespace | + +### 15.2 REPL Workflow + +```clojure +;; Load code +(require '[myapp.core :as core] :reload) + +;; Clear REPL state +(remove-all-methods multidoad :default) + +;; Catch exceptions + CompilerException ... + +;; Pretty print +(require '[clojure.pprint :as pp]) +(pp/pprint data) +``` + +### 15.3 Editor Integration + +- **VS Code + Calva**: `:jack-in` to start REPL +- **Emacs + CIDER**: `cider-jack-in` +- **Vim + Conjure**: Automatically connects + +--- + +## 16. Core.async + +Core.async provides asynchronous programming with channels. + +### 16.1 Channels + +```clojure +(require '[clojure.core.async :as async]) + +(def ch (async/chan)) + +;; Put value (blocks if buffer full) +(async/>!! ch "hello") + +;; Take value (blocks if empty) +(async/ "hello" + +;; Close channel +(async/close! ch) +``` + +### 16.2 Threaded Channels + +```clojure +;; >!! and ! and ! out-ch "result")) +``` + +### 16.4 Buffers + +```clojure +(async/chan 10) ;; Fixed buffer +(async/chan (async/sliding-buffer 100)) ;; Drops old +(async/chan (async/dropping-buffer 100)) ;; Drops new +``` + +### 16.5 Pipeline + +```clojure +(async/pipeline-async 4 + out-ch + (fn [input ch] + (async/go + (async/>! ch (process input)))) + in-ch) +``` + +--- + +## 17. Best Practices + +### 17.1 Code Organization + +```clojure +;; Typical namespace structure +(ns myapp.core + (:require [myapp.util :as util] + [myapp.spec :as spec] + [clojure.string :as str]) + (:import [java.util Date])) ;; Shown for completeness only +``` + +### 17.2 Immutable Data + +Prefer immutable data structures. When mutation is needed: +- Use atoms for independent state +- Use refs with STM for coordinated state +- Avoid side effects in pure functions + +### 17.3 Naming Conventions + +| Type | Convention | Example | +|------|------------|---------| +| Vars | kebab-case | `defn calculate-total` | +| Classes/Records | PascalCase | `defrecord UserProfile` | +| Constants | UPPER-SNAKE | `def MAX-RETRY` | +| Private vars | trailing underscore | `defn- internal-func` | +| Dynamic vars | *surrounded* | `def *max-connections*` | + +### 17.4 Error Handling + +```clojure +(defn safe-parse + [s] + (try + (Long/parseLong s) + (catch NumberFormatException _ + nil))) + +;; With ex-info for structured errors +(defn validate [x] + (when (neg? x) + (throw (ex-info "Must be positive" {:value x})))) +``` + +### 17.5 Performance Tips + +1. Use `transduce` instead of `into` + transformation +2. Use `mapv` when you need a vector result +3. Use `filterv` for filtered vectors +4. Use `reduce-kv` for map iteration +5. Consider `持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持,持持,持,持,持,持,持,持,持,持,持持,持,持,持,持,持,持持,持,持,持,持,持持,持,持,持持,持,持持,持,持,持,持持,持持,持持,持持,持持,持持持,持,持,持,持持,持持,持持,持持,持持持,持,持持,持持持,持持持,持持持,持持持持,持持,持,持持持,持持,持持持,持持持,持持,持持,持持持,持持持,持持持,持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,持持持,1. Use transduce for efficient transformations +2. chunked sequences for better performance +3. prefer reduce over apply for large collections +``` + +### 17.6 Threading Macros + +Make code more readable: + +```clojure +;; Thread-first (->) +(-> user + (assoc :last-login (java.time Instant/now)) + (update :login-count inc) + :last-login) + +;; Thread-last (->>) +(->> users + (map :name) + (filter #(.startsWith % "A")) + (sort) + (take 10)) + +;; Thread-as (some->, some->>) +(some-> {:user {:profile {:avatar "url"}}} + :user :profile :avatar + clojure.string/upper-case) +``` + +--- + +## 18. Index + +### A + +- `atom` - [11.1](#11-atoms) +- `agent` - [11.3](#11-3-agents) +- `and` - [3.5](#35-special-forms) +- `are` - [14.1](#141-clojuretest) +- `apply` - [7.3.3](#733-reduce) +- `as->` - [17.6](#176-threading-macros) +- `assert` - [5.5](#55-pre--and-post-conditions) +- `assoc` - [4.9](#49-maps) +- `async/chan` - [16.1](#161-channels) + +### B + +- `binding` - [11.4](#114-vars) +- `butlast` - [7.3.7](#737-interpose--interleave) + +### C + +- `case` - [6.1.5](#615-case) +- `comment` - [3.5](#35-comments) +- `comp` - [5.3](#53-higher-order-functions) +- `concat` - [4.7](#47-lists) +- `cond` - [6.1.3](#613-cond) +- `condp` - [6.1.4](#614-condp) +- `conj` - [4.8](#48-vectors) +- `cons` - [4.7](#47-lists) +- `def` - [3.3.1](#331-def) +- `defmacro` - [10.1](#101-what-are-macros) +- `defmethod` - [13.1](#131-defining-multimethods) +- `defmulti` - [13.1](#131-defining-multimethods) +- `defn` - [5.1.1](#511-basic-syntax) +- `defprotocol` - [12.1](#121-protocols) +- `defrecord` - [12.2](#122-records) +- `defref` - [11.2](#112-refs) +- `delay` - [11.6](#116-promises-and-delivered) +- `destructure` - [8](#8-destructuring) +- `disj` - [4.10](#410-sets) +- `dissoc` - [4.9](#49-maps) +- `doseq` - [6.2.4](#624-doseq-side-effects) +- `dosync` - [11.2](#112-refs) +- `dotimes` - [6.2.3](#623-for-list-comprehension) +- `drop` - [7.3.6](#736-take--drop) +- `drop-while` - [7.3.6](#736-take--drop) + +### E + +- `empty?` - [4.12](#412-collections-library) +- `extend-protocol` - [12.3](#123-extending-existing-types) +- `extend-type` - [12.3](#123-extending-existing-types) + +### F + +- `fdef` - [5.5](#55-pre--and-post-conditions) +- `filter` - [7.3.2](#732-filter--remove) +- `filterv` - [7.3.2](#732-filter--remove) +- `find-doc` - [15.1](#151-repl-commands) +- `first` - [4.7](#47-lists) +- `flatten` - [7.3.7](#737-interpose--interleave) +- `flip` - [11.2](#112-refs) +- `fn` - [5.2](#52-anonymous-functions) +- `for` - [6.2.3](#623-for-list-comprehension) +- `force` - [11.6](#116-promises-and-delivered) +- `format` - [2.3](#23-editor-setup) +- `future` - [11.5](#115-futures) + +### G + +- `gen-class` - [9.4](#94-ns-macro-options) +- `get` - [4.8](#48-vectors) +- `get-in` - [4.9](#49-maps) +- `group-by` - [7.3.7](#737-interpose--interleave) + +### H + +- `hash-map` - [4.9](#49-maps) +- `hash-set` - [4.10](#410-sets) + +### I + +- `if` - [3.3.3](#333-if) +- `if-let` - [6.1.2](#612-when--when-not) +- `if-not` - [6.1.1](#611-if--if-not) +- `import` - [9.3](#93-referring-and-importing) +- `inc` - [4.2](#42-strings) +- `indexed` - [7.3.7](#737-interpose--interleave) +- `into` - [7.3.7](#737-interpose--interleave) +- `interleave` - [7.3.8](#738-interpose--interleave) +- `interpose` - [7.3.8](#738-interpose--interleave) +- `iterate` - [7.2](#72-lazy-sequences) + +### J + +- `juxt` - [5.3](#53-higher-order-functions) + +### K + +- `keys` - [8.2](#82-map-destructuring) + +### L + +- `let` - [3.3.2](#332-let) +- `letfn` - [5.1.3](#513-variable-arguments) +- `list` - [4.7](#47-lists) +- `list*` - [4.7](#47-lists) +- `load-file` - [15.2](#152-repl-workflow) +- `loop` - [6.2.2](#622-looprecur) + +### M + +- `macroexpand` - [10.5](#105-macro-expansion) +- `macroexpand-1` - [10.5](#105-macro-expansion) +- `map` - [7.3.1](#731-map) +- `map-indexed` - [7.3.1](#731-map) +- `mapcat` - [7.3.5](#735-mapcat) +- `mapv` - [7.3.1](#731-map) +- `max-key` - [5.3](#53-higher-order-functions) +- `merge` - [4.9](#49-maps) +- `merge-with` - [4.9](#49-maps) +- `meta` - [3.3.1](#331-def) +- `min-key` - [5.3](#53-higher-order-functions) +- `mod` - [4.1.1](#411-integer-types) + +### N + +- `namespace` - [9.5](#95-working-with-namespaces) +- `neg?` - [4.2](#42-strings) +- `nil?` - [4.4](#44-booleans) +- `not` - [4.4](#44-booleans) +- `not-empty` - [4.12](#412-collections-library) +- `ns` - [9.1](#91-creating-and-switching-namespaces) +- `ns-publics` - [9.5](#95-working-with-namespaces) +- `ns-resolve` - [9.5](#95-working-with-namespaces) + +### O + +- `or` - [3.5](#35-special-forms) + +### P + +- `parallelize` - [11.7](#117-stm-guidelines) +- `partition` - [7.3.7](#737-interpose--interleave) +- `partition-all` - [7.3.7](#737-interpose--interleave) +- `partition-by` - [7.3.7](#737-interpose--interleave) +- `partial` - [5.3](#53-higher-order-functions) +- `peek` - [4.7](#47-lists) +- `persist` - [7.2](#72-lazy-sequences) +- `pmap` - [7.3.1](#731-map) +- `pop` - [4.8](#48-vectors) +- `pos?` - [4.2](#42-strings) +- `promise` - [11.6](#116-promises-and-delivered) + +### Q + +- `quote` - [3.3.4](#334-quote) + +### R + +- `rand` - [7.4](#74-creating-sequences) +- `rand-int` - [7.4](#74-creating-sequences) +- `range` - [7.4](#74-creating-sequences) +- `recur` - [6.2.1](#621-recursion) +- `reduce` - [7.3.3](#733-reduce) +- `reduce-kv` - [7.3.3](#733-reduce) +- `reductions` - [7.3.3](#733-reduce) +- `ref` - [11.2](#112-refs) +- `ref-set` - [11.2](#112-refs) +- `release-pending-sends` - [11.3](#113-agents) +- `remove` - [7.3.2](#732-filter--remove) +- `repeat` - [7.4](#74-creating-sequences) +- `repeatedly` - [7.4](#74-creating-sequences) +- `replicate` - [7.4](#74-creating-sequences) +- `require` - [9.3](#93-referring-and-importing) +- `reset!` - [11.1](#111-atoms) +- `rest` - [4.7](#47-lists) +- `reverse` - [7.3.9](#739-distinct--sort--shuffle) + +### S + +- `select-keys` - [4.9](#49-maps) +- `send` - [11.3](#113-agents) +- `send-off` - [11.3](#113-agents) +- `seq` - [7.1](#71-the-sequence-abstraction) +- `set` - [4.10](#410-sets) +- `set!` - [11.4](#114-vars) +- `short-circuit` - [3.5](#35-special-forms) +- `shuffle` - [7.3.9](#739-distinct--sort--shuffle) +- `shutdown-agents` - [11.3](#113-agents) +- `some` - [7.3.2](#732-filter--remove) +- `some->` - [17.6](#176-threading-macros) +- `some-fn` - [5.3](#53-higher-order-functions) +- `sort` - [7.3.9](#739-distinct--sort--shuffle) +- `sort-by` - [7.3.9](#739-distinct--sort--shuffle) +- `split-at` - [7.3.6](#736-take--drop) +- `split-with` - [7.3.6](#736-take--drop) +- `str` - [4.2](#42-strings) +- `subs` - [4.2](#42-strings) +- `superiors` - [13.3](#133-hierarchies) +- `swap!` - [11.1](#111-atoms) + +### T + +- `take` - [7.3.6](#736-take--drop) +- `take-nth` - [7.3.6](#736-take--drop) +- `take-while` - [7.3.6](#736-take--drop) +- `test` - [14.1](#141-clojuretest) +- `thread-bound?` - [11.4](#114-vars) +- `throw` - [6.3](#63-exception-handling) +- `tree-seq` - [7.5](#75-walking-collections) +- `try` - [6.3](#63-exception-handling) +- `type` - [12.2](#122-records) + +### U + +- `update` - [4.9](#49-maps) +- `update-in` - [4.9](#49-maps) +- `use` - [9.3](#93-referring-and-importing) + +### V + +- `val` - [7.1](#71-the-sequence-abstraction) +- `vals` - [4.9](#49-maps) +- `var` - [3.3.1](#331-def) +- `var-get` - [11.4](#114-vars) +- `var-set` - [11.4](#114-vars) +- `vec` - [4.8](#48-vectors) +- `vector` - [4.8](#48-vectors) +- `vector-of` - [4.8](#48-vectors) +- `volatile!` - [11.1](#111-atoms) + +### W + +- `when` - [6.1.2](#612-when--when-not) +- `when-bind` - [10.6.2](#1062-conditional-compilation) +- `when-first` - [6.1.2](#612-when--when-not) +- `when-let` - [6.1.2](#612-when--when-not) +- `when-not` - [6.1.2](#612-when--when-not) +- `while` - [6.2](#62-iteration) + +### Z + +- `zero?` - [4.2](#42-strings) +- `zipmap` - [4.9](#49-maps) + +--- + +## Appendix A: Quick Reference + +### Core Functions + +| Function | Description | +|----------|-------------| +| `inc` / `dec` | Increment / decrement | +| `+` / `-` / `*` / `/` | Arithmetic | +| `=` / `==` / `not=` | Equality | +| `<` / `>` / `<=` / `>=` | Comparison | +| `and` / `or` / `not` | Logical | +| `first` / `rest` / `next` | Sequence ops | +| `cons` / `conj` / `concat` | Collection building | +| `map` / `filter` / `reduce` | Transducers | +| `get` / `assoc` / `dissoc` | Map operations | +| `get-in` / `assoc-in` | Nested operations | +| `apply` / `partial` | Function application | +| `comp` / ` juxt` / `memoize` | Function combinators | + +### Data Structure Summary + +| Type | Literal | Access | Immutable? | +|------|---------|--------|------------| +| List | `'(1 2 3)` | `first`, `nth` | Yes | +| Vector | `[1 2 3]` | `get`, `nth` | Yes | +| Map | `{:a 1}` | `get`, `keys` | Yes | +| Set | `#{1 2 3}` | `get`, `contains?` | Yes | + +--- + +## Appendix B: Glossary + +**Atom** - A mutable container that provides synchronous, independent updates. + +**Closure** - A function that captures and retains access to variables from its enclosing scope. + +**Destructuring** - Binding local variables to parts of a collection or map. + +**Hygienic Macro** - A macro that doesn't leak unintended bindings. + +**Lazy Sequence** - A sequence whose elements are computed on-demand. + +**Protocol** - A named set of method signatures that types can implement. + +**Ref** - A mutable container managed by STM for coordinated updates. + +**S-Expression** - A parenthesized expression in Lisp syntax. + +**STM** - Software Transactional Memory, a concurrency model using transactions. + +**Var** - A mutable container that provides thread-local and namespace-scoped state. + +--- + +*Pure Clojure: A Comprehensive Guide* +*Version 1.0* diff --git a/books/clojure-book/en/02-advanced.md b/books/clojure-book/en/02-advanced.md new file mode 100644 index 0000000..2b149a1 --- /dev/null +++ b/books/clojure-book/en/02-advanced.md @@ -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* diff --git a/books/clojure-book/en/03-tooling.md b/books/clojure-book/en/03-tooling.md new file mode 100644 index 0000000..70e9cfe --- /dev/null +++ b/books/clojure-book/en/03-tooling.md @@ -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* diff --git a/books/clojure-book/en/04-recipes.md b/books/clojure-book/en/04-recipes.md new file mode 100644 index 0000000..d2c44ff --- /dev/null +++ b/books/clojure-book/en/04-recipes.md @@ -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 value)) + (recur (! out window) + (let [next-items (vec (take overlap (rest window))) + remaining (- size overlap) + new-items (vec (take remaining (async/ (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* diff --git a/books/clojure-book/en/05-clojure-nim.md b/books/clojure-book/en/05-clojure-nim.md new file mode 100644 index 0000000..cd7cb44 --- /dev/null +++ b/books/clojure-book/en/05-clojure-nim.md @@ -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.* diff --git a/books/clojure-book/en/index.md b/books/clojure-book/en/index.md new file mode 100644 index 0000000..9e2114a --- /dev/null +++ b/books/clojure-book/en/index.md @@ -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)