23252826a0
- New chapter 05-clojure-nim.md (EN + BG) covering: - Native compilation pipeline (Clojure → Nim → C → binary) - AI-powered development (error explanation, code generation) - JSON REPL for AI agents - loop/recur with real TCO - Cross-compilation: JS, shared libs, WASM - Persistent data structures (HAMT) - Concurrency: atoms, agents, channels - Updated book README.md with Clojure/Nim focus - Added Clojure/Nim terms to subject indices (EN + BG) - Removed books/ from .gitignore so it can be pushed to GitLab
711 lines
18 KiB
Markdown
711 lines
18 KiB
Markdown
# Чист 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: Разширени теми*
|