Files
dimgigov 23252826a0 feat: add Clojure/Nim chapter to the book + restructure for GitLab
- New chapter 05-clojure-nim.md (EN + BG) covering:
  - Native compilation pipeline (Clojure → Nim → C → binary)
  - AI-powered development (error explanation, code generation)
  - JSON REPL for AI agents
  - loop/recur with real TCO
  - Cross-compilation: JS, shared libs, WASM
  - Persistent data structures (HAMT)
  - Concurrency: atoms, agents, channels
- Updated book README.md with Clojure/Nim focus
- Added Clojure/Nim terms to subject indices (EN + BG)
- Removed books/ from .gitignore so it can be pushed to GitLab
2026-05-08 23:32:11 +03:00

18 KiB
Raw Permalink Blame History

Чист Clojure: Практически рецепти

Съдържание

  1. Често срещани модели
  2. Рецепти за трансформация на данни
  3. Рецепти за управление на състояние
  4. Async рецепти
  5. Рецепти за валидация
  6. Модели за дизайн на API
  7. Рецепти за тестване
  8. Рецепти за производителност

1. Често срещани модели

1.1 Maybe/Option модел

(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 модел

(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

(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 модел

(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 Вложен достъп до данни

(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 Групиране и агрегиране

;; Групиране по множество ключове
(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

(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 Операции с дървета

;; Сума на всички числови листа
(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

(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

(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 механизъм

(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

(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

(defn channel-pipeline [in-f out-f & channels]
  (doseq [ch channels]
    (async/go
      (loop []
        (when-let [value (<! ch)]
          (out-f value)
          (recur))))))

;; Употреба
(let [input-chan (async/chan 100)
      process-chan (async/chan 100)
      output-chan (async/chan 100)]
  (async/pipeline 10 process-chan (map process-item) input-chan)
  (async/pipeline 10 output-chan (map format-output) process-chan))

4.2 Multiplexing канали

(defn multiplex [in-chan & out-chans]
  (async/go
    (loop [value (<! in-chan)]
      (when-not (nil? value)
        (doseq [ch out-chans]
          (>! ch value))
        (recur (<! in-chan))))))

;; Употреба
(let [input (async/chan)
      out1 (async/chan)
      out2 (async/chan)]
  (multiplex input out1 out2)
  ;; Сега input се broadcast-ва към двата изхода
  )

4.3 Timeout модели

;; Timeout на операция
(async/go
  (let [result (async/alts!! [work-chan
                              (async/timeout 5000)])]
    (if (= result :timed-out)
      {:status :timeout}
      {:status :success :value (first result)})))

;; Retry с exponential backoff
(defn with-retry [f max-attempts delay-ms]
  (async/go
    (loop [attempts 0]
      (let [result (async/<! (async/timeout delay-ms))]
        (if (= :failure result)
          (if (< attempts max-attempts)
            (recur (inc attempts))
            {:status :failed})
          {:status :success :value result})))))

4.4 Windowing

(defn windowed [in size overlap]
  (let [out (async/chan)]
    (async/go
      (loop [window (vec (take size (async/<! in)))]
        (when (seq window)
          (>! out window)
          (let [next-items (vec (take overlap (rest window)))
                remaining (- size overlap)
                new-items (vec (take remaining (async/<! in)))]
            (recur (into next-items new-items))))))
    out))

5. Рецепти за валидация

5.1 Многоетапна валидация

(defn validate-stages [data & validators]
  (reduce
    (fn [result validator]
      (let [errors (validator result)]
        (if (seq errors)
          (reduced {:status :error :errors errors})
          result)))
    {:status :ok :data data}
    validators))

(defn non-blank [field]
  (fn [result]
    (when (clojure.string/blank? (get-in result [:data field]))
      [field "не може да е празно"])))

(defn max-length [field length]
  (fn [result]
    (when (> (count (get-in result [:data field])) length)
      [field (str "трябва да е най-много" length "символа")])))

(validate-stages
  {:name ""}
  (non-blank :name)
  (max-length :name 50))
;; => {:status :error :errors [:name "не може да е празно"]}

5.2 Schema валидация

(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 тестване

(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 (Чисти функции)

(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

(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

(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

(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 тестване

(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

(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 тестване

;; Просто 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 обработка

(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

(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

(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 Паралелна обработка на колекции

(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: Практически рецепти