Advanced Clojure Tutorial

Table of Contents

Prerequisites

  • Solid understanding of Clojure fundamentals (functions, collections, immutability)
  • Experience with basic Clojure development
  • Familiarity with Java interop concepts

Project Setup

(defproject advanced-clojure-tutorial "0.1.0-SNAPSHOT"
  :description "Advanced Clojure Tutorial Project"
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [org.clojure/core.async "1.6.673"]
                 [com.stuartsierra/component "1.1.0"]
                 [org.clojure/spec.alpha "0.3.218"]]
  :main ^:skip-aot tutorial.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all
                      :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})

Morning Session: Protocols, Records, and Reify

Basic Protocol and Record Example

(ns tutorial.shapes)

(defprotocol Drawable
  (draw [this] "Draws the shape")
  (area [this] "Calculates the area"))

(defrecord Circle [radius]
  Drawable
  (draw [this] 
    (str "Drawing circle with radius " radius))
  (area [this]
    (* Math/PI radius radius)))

;; Example usage in comment block for CIDER evaluation
(comment
  (def my-circle (->Circle 5))
  (draw my-circle)
  (area my-circle)
  )

Reify Deep Dive

(ns tutorial.reify-examples
  (:require [tutorial.shapes :refer [Drawable]]))

;; Creating anonymous implementations
(def square
  (reify Drawable
    (draw [this]
      "Drawing a square")
    (area [this]
      100)))

;; Java interface implementation
(def runnable
  (reify java.lang.Runnable
    (run [this]
      (println "Running!"))))

;; Multiple protocol implementation
(defprotocol Scalable
  (scale [this factor]))

(def scalable-circle
  (reify 
    Drawable
    (draw [this] "Drawing circle")
    (area [this] 50)
    
    Scalable
    (scale [this factor]
      (str "Scaling by " factor))))

(comment
  ;; Test implementations
  (draw square)
  (area square)
  (.run runnable)
  (draw scalable-circle)
  (scale scalable-circle 2)
)

Afternoon Session 1: Core.Async

(ns tutorial.async-examples
  (:require [clojure.core.async :refer [chan go >! <! <!! timeout 
                                      close! dropping-buffer sliding-buffer]]))

;; Basic channel operations
(def c (chan))

(defn basic-channel-demo []
  (go (>! c "Hello"))
  (go (println (<! c))))

;; Buffer types demonstration
(defn buffer-examples []
  (let [buffered-chan (chan 10)  ;; Fixed buffer
        dropping-chan (chan (dropping-buffer 10))
        sliding-chan (chan (sliding-buffer 10))]
    ;; Example usage in real code would go here
    ))

;; Advanced patterns
(defn async-processor []
  (let [in (chan)
        out (chan)]
    (go-loop []
      (when-let [val (<! in)]
        (>! out (str val " processed"))
        (recur)))
    [in out]))

;; Pipeline example
(defn process-data []
  (let [[in out] (async-processor)]
    (go
      (doseq [x (range 5)]
        (>! in x)
        (<! (timeout 100)))
      (close! in))
    (go-loop []
      (when-let [result (<! out)]
        (println result)
        (recur)))))

(comment
  (basic-channel-demo)
  (process-data)
  )

Afternoon Session 2: Transducers

(ns tutorial.transducer-examples
  (:require [clojure.core.async :refer [chan]]))

;; Basic transducer composition
(def xform
  (comp
    (map inc)
    (filter even?)
    (take 5)))

;; Custom transducer
(defn dedupe-neighbors
  [step]
  (let [prev (volatile! ::none)]
    (fn
      ([] (step))
      ([result] (step result))
      ([result input]
       (let [prior @prev]
         (vreset! prev input)
         (if (= prior input)
           result
           (step result input)))))))

;; Advanced transducer patterns
(def process-events
  (comp
    (map #(assoc % :timestamp (System/currentTimeMillis)))
    (filter :valid?)
    (dedupe-neighbors)
    (partition-all 100)
    (map #(assoc % :batch-id (random-uuid)))))

;; Example with core.async
(def processing-channel
  (chan 1 process-events))

(comment
  ;; Test basic transducer
  (sequence xform (range 10))
  
  ;; Test custom deduping
  (sequence (dedupe-neighbors) [1 1 2 2 2 3 3 1])
  
  ;; Test process-events
  (let [events [{:valid? true :data "test1"}
                {:valid? true :data "test2"}]]
    (sequence process-events events))
)

Evening Session: Advanced Topics

Component Lifecycle Management

(ns tutorial.component-examples
  (:require [com.stuartsierra.component :as component]))

(defrecord Database [uri connection]
  component/Lifecycle
  (start [this]
    (assoc this :connection (str "Connected to: " uri)))
  (stop [this]
    (println "Disconnecting from:" uri)
    (assoc this :connection nil)))

(defn new-database [uri]
  (map->Database {:uri uri}))

(defn create-system []
  (component/system-map
    :db (new-database "postgresql://localhost:5432/mydb")))

(comment
  (def system (create-system))
  (def started-system (component/start system))
  (def stopped-system (component/stop started-system))
)

Spec Examples

(ns tutorial.spec-examples
  (:require [clojure.spec.alpha :as s]
            [clojure.spec.gen.alpha :as gen]))

(s/def ::age (s/and int? #(>= % 0) #(<= % 150)))
(s/def ::name string?)
(s/def ::person (s/keys :req [::name ::age]
                       :opt [::address]))

(comment
  ;; Validation examples
  (s/valid? ::person {::name "John" ::age 30})
  
  ;; Generation examples
  (gen/generate (s/gen ::person))
  
  ;; Explain examples
  (s/explain ::person {::name "John" ::age -5})
)

Exercises and Projects

Exercise 1: Web Scraper

(ns tutorial.exercises.web-scraper
  (:require [clojure.core.async :refer [chan go >! <! <!! timeout close!]]))

;; TODO: Implement a concurrent web scraper
;; 1. Create channels for URLs and results
;; 2. Implement worker processes using go blocks
;; 3. Add error handling
;; 4. Implement rate limiting

Exercise 2: Data Pipeline

(ns tutorial.exercises.data-pipeline)

;; TODO: Implement a data processing pipeline using transducers
;; 1. Define data transformation steps
;; 2. Compose transducers
;; 3. Add error handling and validation
;; 4. Implement batching and throttling

Best Practices and Tips

  • Use reify for one-off implementations, defrecord for reusable types
  • Always close channels in core.async to prevent memory leaks
  • Consider using transducers early in development for performance benefits
  • Use component or mount for managing application state
  • Leverage spec for validation and documentation

Additional Resources

Setup Instructions

  1. Clone this repository
  2. Open in your favorite editor with CIDER support
  3. Run C-c C-v t (cider-eval-buffer) to tangle the source files
  4. Connect to a REPL and start experimenting!

Notes

  • All code blocks are tangled to appropriate files in the src directory
  • Comment blocks contain examples that can be evaluated directly in CIDER
  • Use C-c C-v t to tangle all source files at once
  • Use C-c C-c to evaluate individual code blocks
  • The project structure follows standard Clojure conventions

Author: Claude

jwalsh@nexus

Last Updated: 2025-07-30 13:45:27

build: 2025-12-23 09:11 | sha: a10ddd7