Files
2026-09-14 09:36:35 -09:00

384 lines
10 KiB
Clojure

;; ============================================
;; 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
;; --- 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]