;; ============================================ ;; learnclj.clj — Clojure Learning Examples ;; ============================================ (ns learnclj (:require [clojure.string :as str])) ;; --- 1. Basics --- ;; Literals 123 ; => 123 (Integer) 3.14 ; => 3.14 (Double) 22/7 ; => 22/7 (Ratio/rational) :keyword ; => :keyword (Keyword) "string" ; => "string" (String) \c ; => \c (Character) true ; => true (Boolean) nil ; => nil (Null/Nothing) ;; === Function Basics === ;; defn — defines a named function (defn greet [name] (str "Hello, " name "!")) (greet "Sam") ; => "Hello, Sam!" ;; Anonymous function — fn form (fn [x y] (+ x y)) ; => #object[clojure.core$fn__... 0x...] ;; Anonymous function — sharp-syntax shorthand (#(+ %1 %2) 3 4) ; => 7 ;; Function with multiple parameters (defn add [x y] (+ x y)) (add 3 4) ; => 7 ;; Default arguments via destructuring in parameters (defn greet-default [name {:keys [formal] :or {formal false}}] (if formal (str "Good day, " name) (str "Hey, " name))) (greet-default "Sam" {}) ; => "Hey, Sam" (greet-default "Sam" {:formal true}) ; => "Good day, Sam" ;; Variadic function — & collects extra args into a seq (defn sum [& numbers] (reduce + 0 numbers)) (sum 1 2 3 4) ; => 10 ;; Named parameters via map destructuring (defn greet-named [& {:keys [name formal] :or {formal false}}] (if formal (str "Good day, " name) (str "Hey, " name))) (greet-named :name "Sam" :formal true) ; => "Good day, Sam" (greet-named :name "Sam") ; => "Hey, Sam" ;; --- 3. Data Structures --- ;; Lists — linked lists, quoted for data '(1 2 3) ; => (1 2 3) (quoted list) (list 1 2 3) ; => (1 2 3) ;; Vectors — indexed, sequential [1 2 3] ; => [1 2 3] (vector "a" "b" "c") ; => ["a" "b" "c"] (get [10 20 30] 1) ; => 20 ;; Maps — key-value pairs {:name "Alice" :age 30} ; => {:name "Alice", :age 30} {"x" 1 "y" 2} ; => {"x" 1, "y" 2} (get {:a 1 :b 2} :a) ; => 1 (:name {:name "Alice"}) ; => "Alice" (keyword as accessor) ;; Sets — unique values #{1 2 3 4 5} ; => #{1 2 3 4 5} (order not guaranteed) (set [1 2 2 3 3 3]) ; => #{1 2 3} (duplicates removed, order not guaranteed) ;; --- 4. Control Flow --- ;; if (if (> 5 3) "yes" "no") ; => "yes" ;; when — like if but for side effects (no else branch) (when true (print "Side effect here") :result) ; => :result ;; cond — multiple conditions (cond (= 1 1) "one" (= 2 2) "two" :else "other") ; => "one" ;; case — exact match dispatch (case 2 1 "one" 2 "two" 3 "three" "other") ; => "two" ;; and / or — short-circuiting (and true false nil) ; => false (returns first falsy value) (or false true 42) ; => true ;; --- 5. Sequences --- ;; map — apply function to each element (map inc [1 2 3]) ; => (2 3 4) (map str ["a" "b"] [1 2]) ; => ("a1" "b2") ;; filter (filter even? [1 2 3 4 5]) ; => (2 4) ;; reduce (reduce + [1 2 3 4]) ; => 10 (reduce str ["a" "b" "c"]) ; => "abc" ;; into — combine collections (into [] (map inc [1 2 3])) ; => [2 3 4] (into {} (map #(vector %1 %2) [:a :b] [1 2])) ; => {:a 1, :b 2} ;; sequence processing (sequence (comp (filter odd?) (map #(* % %))) [1 2 3 4 5 6 7]) ; => (1 9 25 49) ;; === Destructuring === ;; Vector destructuring — bind positions to names (let [[first second & rest] [1 2 3 4 5]] [first second rest]) ; => [1 2 (3 4 5)] ;; Map destructuring — bind keys to names (let [{name :name age :age} {:name "Bob" :age 25}] [name age]) ; => ["Bob" 25] ;; :keys shorthand — automatic key-to-name binding (let [{:keys [name age]} {:name "Bob" :age 25}] [name age]) ; => ["Bob" 25] ;; :or — default values when key is missing or nil (let [{:keys [name] :or {name "Anonymous"}} {:age 25}] name) ; => "Anonymous" ;; Nested destructuring (let [{:keys [x y] :as point} {:point {:x 10 :y 20}} {:keys [x y]} (:point point)] [x y]) ; => [10 20] ;; Destructuring in function parameters (same patterns as let) (defn show-point [{:keys [x y] :or {x 0 y 0}}] (str "Point: (" x ", " y ")")) (show-point {:x 10 :y 20}) ; => "Point: (10, 20)" (show-point {}) ; => "Point: (0, 0)" ;; Destructuring in loop bindings (loop [[first & rest] [1 2 3]] (if first (do (println "Got:" first) (recur rest)) :done)) ; => Got: 1, Got: 2, Got: 3, :done ;; --- 7. Namespaces --- ;; require — load a namespace ;; (require '[clojure.string :as str]) ;; (str/upper-case "hello") ;; use — import functions directly ;; (use '[clojure.string :only [upper-case]]) ;; (upper-case "hello") ;; import — Java classes ;; (import java.util.Date) ;; (Date.) ;; --- 8. Recursion & Tail Recursion --- ;; Regular recursion (defn factorial [n] (if (<= n 1) 1 (* n (factorial (dec n))))) ;; Tail-recursive with loop/recur (defn factorial-tail [n] (loop [n n acc 1] (if (<= n 1) acc (recur (dec n) (* acc n))))) ;; --- 9. Conditionals with when-let / if-let --- ;; when-let — bind and condition in one (when-let [val (get {:a 1} :b)] val) ; => nil (doesn't evaluate body) (if-let [val (get {:a 1} :a)] val "not found") ; => 1 ;; --- 10. Multiline strings & syntax quoting --- ;; Multiline string (def multiline "This is a multiline string") ;; Syntax quoting (backquote) — auto-qualifies vars (defn make-list [] `(~"a" ~"b" "c")) ; => ("a" "b" "c") ;; --- 11. Protocols (OOP-like) --- (defprotocol Shape (area [this]) (perimeter [this])) (deftype Rectangle [width height] Shape (area [this] (* width height)) (perimeter [this] (* 2 (+ width height)))) (defn make-rectangle [w h] (Rectangle. w h)) (make-rectangle 4 5) ; => #object[learnclj.Rectangle ...] (area (make-rectangle 4 5)) ; => 20 (perimeter (make-rectangle 4 5)) ; => 18 ;; --- 12. Records --- (defrecord Person [name age email]) (def alice (->Person "Alice" 30 "alice@example.com")) (:name alice) ; => "Alice" (->Person "Alice" 30 "alice@example.com") ; => #learnclj.Person{:name "Alice", :age 30, :email "alice@example.com"} (map->Person {:name "Bob" :age 25 :email "bob@example.com"}) ; => #learnclj.Person{:name "Bob", :age 25, :email "bob@example.com"} ;; --- 12b. Type & Protocol Extension --- ;; Protocol to define (defprotocol Formattable (format-for-display [this] "Returns a formatted string representation")) ;; extend-type — add protocol methods to an existing type (extend-type Number Formattable (format-for-display [this] (if (integer? this) (str "Int: " this) (str "Num: " this)))) (format-for-display 42) ; => "Int: 42" (format-for-display 3.14) ; => "Num: 3.14" ;; extend-protocol — add protocol methods to multiple types at once (extend-protocol Formattable java.util.Date (format-for-display [this] (str "[Date] " (.toString this))) java.time.LocalDateTime (format-for-display [this] (str "[LocalDateTime] " (.toString this))) java.util.UUID (format-for-display [this] (str "[UUID] " (.toString this)))) (format-for-display (java.util.Date.)) ; => "[Date] ..." (format-for-display (java.util.UUID/randomUUID)) ; => "[UUID] ..." (format-for-display (java.time.LocalDateTime/now)) ; => "[LocalDateTime] ..." ;; --- 13. Atoms & State --- (def counter (atom 0)) (swap! counter inc) ; => 1 (swap! counter + 5) ; => 6 (reset! counter 0) ; => 0 ;; --- 14. Java Interop --- ;; === Method Calls === ;; Direct form: (. object methodName args…) (.toUpperCase "hello") ; => "HELLO" (.substring "hello" 1 3) ; => "el" ;; Field form: (. object fieldName) — for getters (no args) (. "hello" length) ; => 5 (.age alice) ; => 30 ;; Dot-dot (..): chained field access + method call (.. alice email toUpperCase) ; => "ALICE@EXAMPLE.COM" ;; Equivalent to: (. (. alice email) toUpperCase) ;; === Static Methods === ;; (Class/fieldName) or (Class/staticMethod args…) (System/getProperty "user.name") ; => "ajet" (static field/property) (System/getProperty "java.version") ; => "21.0.x" (static method call) ;; === Constructors === ;; (Class.) or (Class. arg1 arg2…) (java.util.Date.) ; => #inst "2026-09-14T..." (current date/time) (java.util.Date. 2024 0 1) ; => #inst "2024-01-01T..." ;; --- 15. Threading Macros --- ;; Thread-first (->) — passes result as first arg ;; Note: (.method args) inside -> is a method call on the threaded value (-> "hello" (.toUpperCase) ; method call: .toUpperCase on "hello" (.replace "H" "J") ; method call: .replace on "HELLO" (.toLowerCase)) ; method call: .toLowerCase on "JELLO" ; => "jello" ;; Thread-last (->>) — passes result as last arg (->> [1 2 3 4 5] (filter odd?) (map #(* % %)) (reduce +)) ; => 35 ;; --- 16. Error Handling --- ;; try/catch (try (/ 1 0) (catch ArithmeticException e (str "Math error: " (.getMessage e)))) ; method call: (.getMessage e) => "Math error: Divide by zero" ;; assert — throws AssertionError on failure (assert (= 2 (+ 1 1))) ; => nil (assert returns nil on success) ;; (assert (= 1 2) "custom message") ; would throw with custom message ;; --- 17. Macros (simplified) --- ;; Define a simple macro (defmacro unless [pred & body] `(if (not ~pred) ~@body)) (unless true (print "Won't print") "result") ; => "result" ;; --- 18. Common Patterns --- ;; Compose functions — comp reads right-to-left (like math) ;; Example: slugify — find word chunks, lowercase, join with dashes (def slugify (comp #(str/join "-" %) #(mapv str/lower-case %) #(re-seq #"\S+" %))) ;; Usage (slugify " Hello World ") ; => "hello-world" (slugify "Foo Bar Baz") ; => "foo-bar-baz" (slugify " Hello World ") ; => "hello-world" ;; Another: trim then uppercase (def trim->upper (comp str/upper-case str/trim)) ;; Usage (trim->upper " hello ") ; => "HELLO" (trim->upper " Clojure is fun ") ; => "CLOJURE IS FUN" ;; Partial application ;; Partial application (def add-5 (partial + 5)) (add-5 3) ; => 8 ;; Complement — negate a predicate (every-pred (complement nil?) (complement zero?)) ; => #object[clojure.core$every_pred ...] ;; Juxt — create multiple functions (def j (juxt :x :y)) ; => #object[clojure.core$juxt ...] (j {:x 1 :y 2}) ; => [1 2]