384 lines
10 KiB
Clojure
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]
|