conjure-js 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/conjure +0 -0
- package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/assets/editor.worker-CdQrwHl8.js +26 -0
- package/dist/assets/main-A7ZMId9A.css +1 -0
- package/dist/assets/main-CmI-7epE.js +3137 -0
- package/dist/index.html +195 -0
- package/dist/vite.svg +1 -0
- package/package.json +68 -0
- package/src/bin/__fixtures__/smoke/app/lib.clj +4 -0
- package/src/bin/__fixtures__/smoke/app/main.clj +4 -0
- package/src/bin/__fixtures__/smoke/repl-smoke.ts +12 -0
- package/src/bin/bencode.ts +205 -0
- package/src/bin/cli.ts +250 -0
- package/src/bin/nrepl-utils.ts +59 -0
- package/src/bin/nrepl.ts +393 -0
- package/src/bin/version.ts +4 -0
- package/src/clojure/core.clj +620 -0
- package/src/clojure/core.clj.d.ts +189 -0
- package/src/clojure/demo/math.clj +16 -0
- package/src/clojure/demo/math.clj.d.ts +4 -0
- package/src/clojure/demo.clj +42 -0
- package/src/clojure/demo.clj.d.ts +0 -0
- package/src/clojure/generated/builtin-namespace-registry.ts +14 -0
- package/src/clojure/generated/clojure-core-source.ts +623 -0
- package/src/clojure/generated/clojure-string-source.ts +196 -0
- package/src/clojure/string.clj +192 -0
- package/src/clojure/string.clj.d.ts +25 -0
- package/src/core/assertions.ts +134 -0
- package/src/core/conversions.ts +108 -0
- package/src/core/core-env.ts +58 -0
- package/src/core/env.ts +78 -0
- package/src/core/errors.ts +39 -0
- package/src/core/evaluator/apply.ts +114 -0
- package/src/core/evaluator/arity.ts +174 -0
- package/src/core/evaluator/collections.ts +25 -0
- package/src/core/evaluator/destructure.ts +247 -0
- package/src/core/evaluator/dispatch.ts +73 -0
- package/src/core/evaluator/evaluate.ts +100 -0
- package/src/core/evaluator/expand.ts +79 -0
- package/src/core/evaluator/index.ts +72 -0
- package/src/core/evaluator/quasiquote.ts +87 -0
- package/src/core/evaluator/recur-check.ts +109 -0
- package/src/core/evaluator/special-forms.ts +517 -0
- package/src/core/factories.ts +155 -0
- package/src/core/gensym.ts +9 -0
- package/src/core/index.ts +76 -0
- package/src/core/positions.ts +38 -0
- package/src/core/printer.ts +86 -0
- package/src/core/reader.ts +559 -0
- package/src/core/scanners.ts +93 -0
- package/src/core/session.ts +610 -0
- package/src/core/stdlib/arithmetic.ts +361 -0
- package/src/core/stdlib/atoms.ts +88 -0
- package/src/core/stdlib/collections.ts +784 -0
- package/src/core/stdlib/errors.ts +81 -0
- package/src/core/stdlib/hof.ts +307 -0
- package/src/core/stdlib/meta.ts +48 -0
- package/src/core/stdlib/predicates.ts +240 -0
- package/src/core/stdlib/regex.ts +238 -0
- package/src/core/stdlib/strings.ts +311 -0
- package/src/core/stdlib/transducers.ts +256 -0
- package/src/core/stdlib/utils.ts +287 -0
- package/src/core/tokenizer.ts +437 -0
- package/src/core/transformations.ts +75 -0
- package/src/core/types.ts +258 -0
- package/src/main.ts +1 -0
- package/src/monaco-esm.d.ts +7 -0
- package/src/playground/clojure-tokens.ts +67 -0
- package/src/playground/editor.worker.ts +5 -0
- package/src/playground/find-form.ts +138 -0
- package/src/playground/playground.ts +342 -0
- package/src/playground/samples/00-welcome.clj +385 -0
- package/src/playground/samples/01-collections.clj +191 -0
- package/src/playground/samples/02-higher-order-functions.clj +215 -0
- package/src/playground/samples/03-destructuring.clj +194 -0
- package/src/playground/samples/04-strings-and-regex.clj +202 -0
- package/src/playground/samples/05-error-handling.clj +212 -0
- package/src/repl/repl.ts +116 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
(ns user.hof
|
|
2
|
+
(:require [clojure.string :as str]))
|
|
3
|
+
|
|
4
|
+
;; Deep Dive: Higher-Order Functions & Transducers
|
|
5
|
+
;;
|
|
6
|
+
;; Press ⌘+Enter on any form to evaluate it.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
;; map
|
|
10
|
+
|
|
11
|
+
(comment
|
|
12
|
+
;; Basic: apply f to every element
|
|
13
|
+
(map inc [1 2 3 4 5]) ;; => (2 3 4 5 6)
|
|
14
|
+
(map str [:a :b :c]) ;; => ("a" "b" "c")
|
|
15
|
+
(map count ["hi" "hello" "hey"]) ;; => (2 5 3)
|
|
16
|
+
|
|
17
|
+
(map (fn [x] (* x x)) (range 1 6)) ;; => (1 4 9 16 25)
|
|
18
|
+
(map #(* % %) (range 1 6)) ;; same, shorter syntax
|
|
19
|
+
|
|
20
|
+
;; Multi-collection: zips and stops at the shortest
|
|
21
|
+
(map + [1 2 3] [10 20 30]) ;; => (11 22 33)
|
|
22
|
+
(map vector [:a :b :c] [1 2 3]) ;; => ([:a 1] [:b 2] [:c 3])
|
|
23
|
+
(map + [1 2 3] [10 20 30] [100 200 300]) ;; => (111 222 333)
|
|
24
|
+
|
|
25
|
+
;; map-indexed: f receives [index value]
|
|
26
|
+
(map-indexed vector [:a :b :c]) ;; => ([0 :a] [1 :b] [2 :c])
|
|
27
|
+
(map-indexed (fn [i v] (str i ": " v))
|
|
28
|
+
["alice" "bob" "carol"]) ;; => ("0: alice" "1: bob" "2: carol")
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
;; filter / remove
|
|
33
|
+
|
|
34
|
+
(comment
|
|
35
|
+
(filter even? [1 2 3 4 5 6]) ;; => (2 4 6)
|
|
36
|
+
(filter string? [1 "a" :b "c" 2]) ;; => ("a" "c")
|
|
37
|
+
(filter :active [{:name "a" :active true}
|
|
38
|
+
{:name "b" :active false}
|
|
39
|
+
{:name "c" :active true}])
|
|
40
|
+
;; => ({:name "a" :active true} {:name "c" :active true})
|
|
41
|
+
|
|
42
|
+
(remove even? [1 2 3 4 5 6]) ;; => (1 3 5)
|
|
43
|
+
(remove nil? [1 nil 2 nil 3]) ;; => (1 2 3)
|
|
44
|
+
|
|
45
|
+
(filter #(> (count %) 3) ["hi" "hello" "hey" "howdy"])
|
|
46
|
+
;; => ("hello" "howdy")
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
;; reduce
|
|
51
|
+
;;
|
|
52
|
+
;; The Swiss army knife — it can implement almost everything else.
|
|
53
|
+
|
|
54
|
+
(comment
|
|
55
|
+
;; Two-arity: uses first two elements to start
|
|
56
|
+
(reduce + [1 2 3 4 5]) ;; => 15
|
|
57
|
+
(reduce * [1 2 3 4 5]) ;; => 120
|
|
58
|
+
(reduce str ["a" "b" "c"]) ;; => "abc"
|
|
59
|
+
|
|
60
|
+
;; Three-arity: explicit initial accumulator
|
|
61
|
+
(reduce + 100 [1 2 3]) ;; => 106
|
|
62
|
+
(reduce conj [] '(1 2 3)) ;; => [1 2 3]
|
|
63
|
+
(reduce (fn [m [k v]] (assoc m k v))
|
|
64
|
+
{}
|
|
65
|
+
[[:a 1] [:b 2] [:c 3]]) ;; => {:a 1 :b 2 :c 3}
|
|
66
|
+
|
|
67
|
+
;; Building a frequency map from scratch
|
|
68
|
+
(reduce (fn [acc x]
|
|
69
|
+
(update acc x (fnil inc 0)))
|
|
70
|
+
{}
|
|
71
|
+
[:a :b :a :c :b :a]) ;; => {:a 3 :b 2 :c 1}
|
|
72
|
+
|
|
73
|
+
;; Early termination with `reduced` — wraps a value to signal "stop now"
|
|
74
|
+
(reduce (fn [acc x]
|
|
75
|
+
(if (nil? x)
|
|
76
|
+
(reduced acc)
|
|
77
|
+
(conj acc x)))
|
|
78
|
+
[]
|
|
79
|
+
[1 2 3 nil 4 5]) ;; => [1 2 3] (stopped at nil)
|
|
80
|
+
|
|
81
|
+
(reduce (fn [_ x]
|
|
82
|
+
(when (> x 100) (reduced x)))
|
|
83
|
+
nil
|
|
84
|
+
(range 1000)) ;; => 101
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
;; apply, partial, comp
|
|
89
|
+
|
|
90
|
+
(comment
|
|
91
|
+
;; apply — call f with a collection as its argument list
|
|
92
|
+
(apply + [1 2 3 4]) ;; => 10
|
|
93
|
+
(apply str ["a" "b" "c"]) ;; => "abc"
|
|
94
|
+
(apply max [3 1 4 1 5 9 2 6]) ;; => 9
|
|
95
|
+
|
|
96
|
+
;; Leading fixed args before the collection
|
|
97
|
+
(apply str "prefix-" ["a" "b"]) ;; => "prefix-ab"
|
|
98
|
+
|
|
99
|
+
;; partial — fix some leading arguments
|
|
100
|
+
(def add10 (partial + 10))
|
|
101
|
+
(add10 5) ;; => 15
|
|
102
|
+
(map add10 [1 2 3]) ;; => (11 12 13)
|
|
103
|
+
|
|
104
|
+
(def greet (partial str "Hello, "))
|
|
105
|
+
(greet "World!") ;; => "Hello, World!"
|
|
106
|
+
|
|
107
|
+
;; comp — compose right-to-left
|
|
108
|
+
(def clean (comp str/trim str/lower-case))
|
|
109
|
+
(clean " HELLO ") ;; => "hello"
|
|
110
|
+
|
|
111
|
+
((comp inc inc inc) 0) ;; => 3
|
|
112
|
+
((comp str/upper-case str/trim) " hello ") ;; => "HELLO"
|
|
113
|
+
|
|
114
|
+
;; identity — returns its argument unchanged
|
|
115
|
+
(filter identity [1 nil 2 false 3]) ;; => (1 2 3)
|
|
116
|
+
|
|
117
|
+
;; constantly — returns a function that always returns the same value
|
|
118
|
+
((constantly 42) 1 2 3) ;; => 42
|
|
119
|
+
(map (constantly :x) [1 2 3]) ;; => (:x :x :x)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
;; complement, juxt, some, every?
|
|
124
|
+
|
|
125
|
+
(comment
|
|
126
|
+
;; complement — logical NOT of a predicate
|
|
127
|
+
(def not-even? (complement even?))
|
|
128
|
+
(filter not-even? [1 2 3 4 5]) ;; => (1 3 5)
|
|
129
|
+
|
|
130
|
+
;; juxt — call multiple functions on the same value, collect results
|
|
131
|
+
((juxt :name :role) {:name "Alice" :role :admin}) ;; => ["Alice" :admin]
|
|
132
|
+
(map (juxt identity #(* % %)) [1 2 3 4 5])
|
|
133
|
+
;; => ([1 1] [2 4] [3 9] [4 16] [5 25])
|
|
134
|
+
|
|
135
|
+
;; some — return first truthy result of (f x), or nil
|
|
136
|
+
(some even? [1 3 5 6 7]) ;; => true
|
|
137
|
+
(some even? [1 3 5 7]) ;; => nil
|
|
138
|
+
(some #(when (> % 3) %) [1 2 3 4 5]) ;; => 4
|
|
139
|
+
|
|
140
|
+
;; every? — true if (f x) is truthy for all elements
|
|
141
|
+
(every? even? [2 4 6]) ;; => true
|
|
142
|
+
(every? even? [2 4 5]) ;; => false
|
|
143
|
+
|
|
144
|
+
(not-any? odd? [2 4 6]) ;; => true
|
|
145
|
+
(not-every? odd? [1 2 3]) ;; => true
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
;; sort, sort-by, group-by, frequencies
|
|
150
|
+
|
|
151
|
+
(def people
|
|
152
|
+
[{:name "Carol" :age 32 :dept :eng}
|
|
153
|
+
{:name "Alice" :age 28 :dept :design}
|
|
154
|
+
{:name "Bob" :age 35 :dept :eng}
|
|
155
|
+
{:name "Dave" :age 28 :dept :design}])
|
|
156
|
+
|
|
157
|
+
(comment
|
|
158
|
+
(sort [3 1 4 1 5 9 2 6]) ;; => (1 1 2 3 4 5 6 9)
|
|
159
|
+
(sort > [3 1 4 1 5 9 2 6]) ;; => (9 6 5 4 3 2 1 1)
|
|
160
|
+
(sort ["banana" "apple" "cherry"]) ;; => ("apple" "banana" "cherry")
|
|
161
|
+
|
|
162
|
+
(sort-by :age people) ;; youngest first
|
|
163
|
+
(sort-by :name people) ;; alphabetical
|
|
164
|
+
|
|
165
|
+
(group-by :dept people) ;; => {:eng [...] :design [...]}
|
|
166
|
+
(group-by :age people) ;; groups by age
|
|
167
|
+
|
|
168
|
+
(frequencies [:a :b :a :c :b :a]) ;; => {:a 3 :b 2 :c 1}
|
|
169
|
+
(distinct [1 2 3 1 2 4]) ;; => (1 2 3 4)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
;; Transducers
|
|
174
|
+
;;
|
|
175
|
+
;; Composable transformation pipelines decoupled from the source and sink.
|
|
176
|
+
;; A 1-arg call to map/filter/etc returns a transducer instead of a result.
|
|
177
|
+
;; Transducer `comp` applies LEFT-to-RIGHT (unlike function comp).
|
|
178
|
+
|
|
179
|
+
(comment
|
|
180
|
+
;; `into` with a transducer
|
|
181
|
+
(into [] (map inc) [1 2 3 4 5]) ;; => [2 3 4 5 6]
|
|
182
|
+
(into [] (filter even?) [1 2 3 4 5 6]) ;; => [2 4 6]
|
|
183
|
+
|
|
184
|
+
;; Chain with comp — one pass, no intermediate sequences
|
|
185
|
+
(into []
|
|
186
|
+
(comp (filter odd?)
|
|
187
|
+
(map #(* % %)))
|
|
188
|
+
[1 2 3 4 5 6 7])
|
|
189
|
+
;; => [1 9 25 49] (squares of odd numbers)
|
|
190
|
+
|
|
191
|
+
;; `transduce` — apply a transducer with reduce semantics
|
|
192
|
+
(transduce (comp (filter odd?)
|
|
193
|
+
(map #(* % %)))
|
|
194
|
+
+
|
|
195
|
+
[1 2 3 4 5 6 7])
|
|
196
|
+
;; => 84 (sum of squares of odds)
|
|
197
|
+
|
|
198
|
+
;; `sequence` — lazy sequence from a transducer
|
|
199
|
+
(sequence (comp (filter even?)
|
|
200
|
+
(map #(/ % 2)))
|
|
201
|
+
(range 1 11))
|
|
202
|
+
;; => (1 2 3 4 5)
|
|
203
|
+
|
|
204
|
+
;; partition-all — group into chunks
|
|
205
|
+
(into [] (partition-all 3) (range 10))
|
|
206
|
+
;; => [[0 1 2] [3 4 5] [6 7 8] [9]]
|
|
207
|
+
|
|
208
|
+
;; dedupe — remove consecutive duplicates
|
|
209
|
+
(into [] (dedupe) [1 1 2 3 3 3 4 1])
|
|
210
|
+
;; => [1 2 3 4 1]
|
|
211
|
+
|
|
212
|
+
;; take as a transducer — stops early, never touches the rest
|
|
213
|
+
(into [] (take 3) (range 1000))
|
|
214
|
+
;; => [0 1 2]
|
|
215
|
+
)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
(ns user.destructuring)
|
|
2
|
+
|
|
3
|
+
;; Deep Dive: Destructuring
|
|
4
|
+
;;
|
|
5
|
+
;; Bind names to parts of a data structure in one step.
|
|
6
|
+
;; Works in `let`, `fn` params, `defn` params, `loop`, and `defmacro`.
|
|
7
|
+
;;
|
|
8
|
+
;; Press ⌘+Enter on any form to evaluate it.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
;; Vector (Sequential) Destructuring
|
|
12
|
+
;;
|
|
13
|
+
;; Bind names to positions, left to right.
|
|
14
|
+
|
|
15
|
+
(comment
|
|
16
|
+
(let [[a b c] [10 20 30]]
|
|
17
|
+
(+ a b c)) ;; => 60
|
|
18
|
+
|
|
19
|
+
;; Skip positions with _
|
|
20
|
+
(let [[_ second _ fourth] [1 2 3 4]]
|
|
21
|
+
[second fourth]) ;; => [2 4]
|
|
22
|
+
|
|
23
|
+
;; Fewer bindings than elements — extras are ignored
|
|
24
|
+
(let [[a b] [1 2 3 4 5]]
|
|
25
|
+
[a b]) ;; => [1 2]
|
|
26
|
+
|
|
27
|
+
;; & rest — bind remaining elements as a sequence
|
|
28
|
+
(let [[first-item & the-rest] [1 2 3 4 5]]
|
|
29
|
+
{:first first-item
|
|
30
|
+
:rest the-rest}) ;; => {:first 1 :rest (2 3 4 5)}
|
|
31
|
+
|
|
32
|
+
;; :as — bind the whole collection in addition to parts
|
|
33
|
+
(let [[x y :as all] [1 2 3]]
|
|
34
|
+
{:x x :y y :all all}) ;; => {:x 1 :y 2 :all [1 2 3]}
|
|
35
|
+
|
|
36
|
+
;; Nested vectors
|
|
37
|
+
(let [[a [b c] d] [1 [2 3] 4]]
|
|
38
|
+
[a b c d]) ;; => [1 2 3 4]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
;; Map Destructuring
|
|
43
|
+
;;
|
|
44
|
+
;; Bind names to values by key.
|
|
45
|
+
|
|
46
|
+
(comment
|
|
47
|
+
;; Basic: bind local name to the value at a key
|
|
48
|
+
(let [{n :name a :age} {:name "Alice" :age 30 :role :admin}]
|
|
49
|
+
(str n " is " a)) ;; => "Alice is 30"
|
|
50
|
+
|
|
51
|
+
;; :keys — shorthand when local name == keyword name
|
|
52
|
+
(let [{:keys [name age role]} {:name "Alice" :age 30 :role :admin}]
|
|
53
|
+
[name age role]) ;; => ["Alice" 30 :admin]
|
|
54
|
+
|
|
55
|
+
;; :strs — like :keys but for string keys
|
|
56
|
+
(let [{:strs [name age]} {"name" "Bob" "age" 25}]
|
|
57
|
+
[name age]) ;; => ["Bob" 25]
|
|
58
|
+
|
|
59
|
+
;; :as — bind the whole map too
|
|
60
|
+
(let [{:keys [name] :as person} {:name "Carol" :age 32}]
|
|
61
|
+
{:greeting (str "Hello " name)
|
|
62
|
+
:full person})
|
|
63
|
+
|
|
64
|
+
;; :or — default values when key is absent (NOT when value is nil)
|
|
65
|
+
(let [{:keys [name role] :or {role :guest}} {:name "Dave"}]
|
|
66
|
+
[name role]) ;; => ["Dave" :guest] (:role was absent)
|
|
67
|
+
|
|
68
|
+
;; :or does NOT apply when the key IS present but value is nil
|
|
69
|
+
(let [{:keys [role] :or {role :guest}} {:role nil}]
|
|
70
|
+
role) ;; => nil (key exists, :or doesn't fire)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
;; Destructuring in Function Params
|
|
75
|
+
|
|
76
|
+
;; Vector destructuring in fn params
|
|
77
|
+
(defn sum-pair [[a b]]
|
|
78
|
+
(+ a b))
|
|
79
|
+
|
|
80
|
+
;; Map destructuring in fn params
|
|
81
|
+
(defn greet-user [{:keys [name role] :or {role :guest}}]
|
|
82
|
+
(str "Hello " name " (" (clojure.core/name role) ")"))
|
|
83
|
+
|
|
84
|
+
;; Multi-arg with map destructuring
|
|
85
|
+
(defn move [{:keys [x y]} {:keys [dx dy]}]
|
|
86
|
+
{:x (+ x dx) :y (+ y dy)})
|
|
87
|
+
|
|
88
|
+
(comment
|
|
89
|
+
(sum-pair [3 7]) ;; => 10
|
|
90
|
+
(greet-user {:name "Alice" :role :admin}) ;; => "Hello Alice (admin)"
|
|
91
|
+
(greet-user {:name "Bob"}) ;; => "Hello Bob (guest)"
|
|
92
|
+
(move {:x 0 :y 0} {:dx 3 :dy 5}) ;; => {:x 3 :y 5}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
;; Nested Destructuring
|
|
97
|
+
|
|
98
|
+
(comment
|
|
99
|
+
;; Map inside vector
|
|
100
|
+
(let [[{:keys [name]} {:keys [score]}]
|
|
101
|
+
[{:name "Alice"} {:score 95}]]
|
|
102
|
+
(str name ": " score)) ;; => "Alice: 95"
|
|
103
|
+
|
|
104
|
+
;; Vector inside map
|
|
105
|
+
(let [{:keys [name]
|
|
106
|
+
[first-score] :scores}
|
|
107
|
+
{:name "Bob" :scores [87 90 95]}]
|
|
108
|
+
(str name " first: " first-score)) ;; => "Bob first: 87"
|
|
109
|
+
|
|
110
|
+
;; Deeply nested — a realistic API response shape
|
|
111
|
+
(def response
|
|
112
|
+
{:status 200
|
|
113
|
+
:body {:user {:id 42
|
|
114
|
+
:name "Alice"
|
|
115
|
+
:tags ["admin" "beta"]}}})
|
|
116
|
+
|
|
117
|
+
(let [{:keys [status]
|
|
118
|
+
{:keys [user]} :body} response
|
|
119
|
+
{:keys [id name]
|
|
120
|
+
[first-tag] :tags} user]
|
|
121
|
+
{:status status :id id :name name :first-tag first-tag})
|
|
122
|
+
;; => {:status 200 :id 42 :name "Alice" :first-tag "admin"}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
;; Destructuring in loop/recur
|
|
127
|
+
|
|
128
|
+
(comment
|
|
129
|
+
(loop [[x & xs] [1 2 3 4 5]
|
|
130
|
+
acc 0]
|
|
131
|
+
(if x
|
|
132
|
+
(recur xs (+ acc x))
|
|
133
|
+
acc)) ;; => 15
|
|
134
|
+
|
|
135
|
+
(loop [{:keys [n acc]} {:n 5 :acc 1}]
|
|
136
|
+
(if (zero? n)
|
|
137
|
+
acc
|
|
138
|
+
(recur {:n (dec n) :acc (* acc n)})))
|
|
139
|
+
;; => 120 (5!)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
;; Kwargs Destructuring (& {:keys})
|
|
144
|
+
;;
|
|
145
|
+
;; `& rest` where rest is treated as a flat key/value sequence.
|
|
146
|
+
|
|
147
|
+
(defn configure [& {:keys [host port timeout]
|
|
148
|
+
:or {host "localhost"
|
|
149
|
+
port 8080
|
|
150
|
+
timeout 5000}}]
|
|
151
|
+
{:host host :port port :timeout timeout})
|
|
152
|
+
|
|
153
|
+
(comment
|
|
154
|
+
(configure) ;; all defaults
|
|
155
|
+
(configure :port 3000)
|
|
156
|
+
(configure :host "prod.example.com" :port 443 :timeout 30000)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
;; Qualified :keys
|
|
161
|
+
;;
|
|
162
|
+
;; When map keys are namespaced keywords, the local name is the unqualified part.
|
|
163
|
+
|
|
164
|
+
(comment
|
|
165
|
+
(let [{:keys [user/name user/role]}
|
|
166
|
+
{:user/name "Alice" :user/role :admin}]
|
|
167
|
+
[name role]) ;; => ["Alice" :admin]
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
;; Practical Patterns
|
|
172
|
+
|
|
173
|
+
(defn summarize [{:keys [name scores]}]
|
|
174
|
+
{:name name
|
|
175
|
+
:average (/ (reduce + scores) (count scores))
|
|
176
|
+
:best (apply max scores)})
|
|
177
|
+
|
|
178
|
+
(def students
|
|
179
|
+
[{:name "Alice" :scores [88 92 95]}
|
|
180
|
+
{:name "Bob" :scores [75 80 78]}
|
|
181
|
+
{:name "Carol" :scores [95 98 100]}])
|
|
182
|
+
|
|
183
|
+
(comment
|
|
184
|
+
(map summarize students)
|
|
185
|
+
|
|
186
|
+
(->> students
|
|
187
|
+
(map summarize)
|
|
188
|
+
(sort-by :average >)
|
|
189
|
+
(map :name)) ;; => ("Carol" "Alice" "Bob")
|
|
190
|
+
|
|
191
|
+
(let [{:keys [scores]} (first students)
|
|
192
|
+
[best & _] (sort > scores)]
|
|
193
|
+
best) ;; => 95
|
|
194
|
+
)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
(ns user.strings-regex
|
|
2
|
+
(:require [clojure.string :as str]))
|
|
3
|
+
|
|
4
|
+
;; Deep Dive: Strings & Regex
|
|
5
|
+
;;
|
|
6
|
+
;; Press ⌘+Enter on any form to evaluate it.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
;; Building & Inspecting Strings
|
|
10
|
+
|
|
11
|
+
(comment
|
|
12
|
+
;; str — concatenate anything into a string
|
|
13
|
+
(str "hello" " " "world") ;; => "hello world"
|
|
14
|
+
(str :keyword) ;; => ":keyword"
|
|
15
|
+
(str 42) ;; => "42"
|
|
16
|
+
(str nil) ;; => "" (nil becomes empty string)
|
|
17
|
+
(str true false) ;; => "truefalse"
|
|
18
|
+
|
|
19
|
+
(subs "hello world" 6) ;; => "world"
|
|
20
|
+
(subs "hello world" 0 5) ;; => "hello"
|
|
21
|
+
|
|
22
|
+
(count "hello") ;; => 5
|
|
23
|
+
(count "") ;; => 0
|
|
24
|
+
|
|
25
|
+
(string? "hello") ;; => true
|
|
26
|
+
(string? :not-a-string) ;; => false
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
;; clojure.string (required as str)
|
|
31
|
+
|
|
32
|
+
(comment
|
|
33
|
+
;; Case
|
|
34
|
+
(str/upper-case "hello") ;; => "HELLO"
|
|
35
|
+
(str/lower-case "WORLD") ;; => "world"
|
|
36
|
+
(str/capitalize "hello world") ;; => "Hello world"
|
|
37
|
+
|
|
38
|
+
;; Trimming whitespace
|
|
39
|
+
(str/trim " hello ") ;; => "hello"
|
|
40
|
+
(str/triml " hello ") ;; => "hello " (left only)
|
|
41
|
+
(str/trimr " hello ") ;; => " hello" (right only)
|
|
42
|
+
(str/trim-newline "hello\n") ;; => "hello"
|
|
43
|
+
|
|
44
|
+
;; Joining
|
|
45
|
+
(str/join ", " ["one" "two" "three"]) ;; => "one, two, three"
|
|
46
|
+
(str/join ["a" "b" "c"]) ;; => "abc"
|
|
47
|
+
|
|
48
|
+
;; Splitting
|
|
49
|
+
(str/split "a,b,c,d" #",") ;; => ["a" "b" "c" "d"]
|
|
50
|
+
(str/split "hello world" #"\s+") ;; => ["hello" "world"]
|
|
51
|
+
(str/split-lines "one\ntwo\nthree") ;; => ["one" "two" "three"]
|
|
52
|
+
|
|
53
|
+
;; Search predicates
|
|
54
|
+
(str/includes? "hello world" "world") ;; => true
|
|
55
|
+
(str/starts-with? "hello world" "hello") ;; => true
|
|
56
|
+
(str/ends-with? "hello world" "world") ;; => true
|
|
57
|
+
(str/blank? " ") ;; => true
|
|
58
|
+
(str/blank? " x ") ;; => false
|
|
59
|
+
|
|
60
|
+
(str/index-of "hello world" "world") ;; => 6
|
|
61
|
+
(str/last-index-of "abcabc" "b") ;; => 4
|
|
62
|
+
|
|
63
|
+
(str/reverse "hello") ;; => "olleh"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
;; Replace
|
|
68
|
+
|
|
69
|
+
(comment
|
|
70
|
+
;; Literal match
|
|
71
|
+
(str/replace "hello world" "world" "Clojure") ;; => "hello Clojure"
|
|
72
|
+
|
|
73
|
+
;; Regex — all matches
|
|
74
|
+
(str/replace "hello world" #"[aeiou]" "*") ;; => "h*ll* w*rld"
|
|
75
|
+
|
|
76
|
+
;; Regex + function — receives match vector, returns replacement string
|
|
77
|
+
(str/replace "hello world"
|
|
78
|
+
#"\b\w"
|
|
79
|
+
(fn [[match]] (str/upper-case match)))
|
|
80
|
+
;; => "Hello World"
|
|
81
|
+
|
|
82
|
+
;; replace-first — only the first occurrence
|
|
83
|
+
(str/replace-first "aabbaabb" "b" "X") ;; => "aaXbaabb"
|
|
84
|
+
(str/replace-first "hello" #"[aeiou]" "*") ;; => "h*llo"
|
|
85
|
+
|
|
86
|
+
;; escape — apply a substitution map to every character
|
|
87
|
+
(str/escape "hello & <world>" {\& "&" \< "<" \> ">"})
|
|
88
|
+
;; => "hello & <world>"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
;; Strings as Sequences
|
|
93
|
+
;;
|
|
94
|
+
;; Strings are seqable — all sequence functions work on them.
|
|
95
|
+
|
|
96
|
+
(comment
|
|
97
|
+
(seq "hello") ;; => ("h" "e" "l" "l" "o")
|
|
98
|
+
|
|
99
|
+
(first "hello") ;; => "h"
|
|
100
|
+
(rest "hello") ;; => ("e" "l" "l" "o")
|
|
101
|
+
(last "hello") ;; => "o"
|
|
102
|
+
|
|
103
|
+
(count "hello") ;; => 5
|
|
104
|
+
|
|
105
|
+
(map str/upper-case (seq "hello")) ;; => ("H" "E" "L" "L" "O")
|
|
106
|
+
|
|
107
|
+
;; Set literals not supported yet — use an explicit membership check:
|
|
108
|
+
(filter (fn [c] (some #(= c %) ["a" "e" "i" "o" "u"])) (seq "hello world"))
|
|
109
|
+
;; => ("e" "o" "o") (vowels only)
|
|
110
|
+
|
|
111
|
+
;; Rebuild a string after seq manipulation
|
|
112
|
+
(apply str (filter (fn [c] (some #(= c %) ["a" "e" "i" "o" "u"])) (seq "hello world")))
|
|
113
|
+
;; => "eoo"
|
|
114
|
+
|
|
115
|
+
(count "café") ;; => 4 (not byte count)
|
|
116
|
+
(seq "café") ;; => ("c" "a" "f" "é")
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
;; Regex Literals
|
|
121
|
+
;;
|
|
122
|
+
;; Patterns follow JavaScript regex rules.
|
|
123
|
+
|
|
124
|
+
(comment
|
|
125
|
+
#"[0-9]+" ;; => #"[0-9]+"
|
|
126
|
+
|
|
127
|
+
;; re-find — first match (string if no groups, vector if groups)
|
|
128
|
+
(re-find #"\d+" "abc123def456") ;; => "123"
|
|
129
|
+
(re-find #"(\w+)@(\w+)" "me@example.com")
|
|
130
|
+
;; => ["me@example.com" "me" "example"] (full match + groups)
|
|
131
|
+
|
|
132
|
+
;; re-matches — match against the ENTIRE string
|
|
133
|
+
(re-matches #"\d+" "123") ;; => "123"
|
|
134
|
+
(re-matches #"\d+" "123abc") ;; => nil (not entire string)
|
|
135
|
+
(re-matches #"(\d{4})-(\d{2})-(\d{2})" "2024-03-15")
|
|
136
|
+
;; => ["2024-03-15" "2024" "03" "15"]
|
|
137
|
+
|
|
138
|
+
;; re-seq — all matches as a lazy sequence
|
|
139
|
+
(re-seq #"\d+" "abc123def456ghi789") ;; => ("123" "456" "789")
|
|
140
|
+
(re-seq #"\b\w{4}\b" "the quick brown fox")
|
|
141
|
+
;; => ("quick" "brown") (4-letter words)
|
|
142
|
+
|
|
143
|
+
;; re-pattern — create a regex from a string (useful when dynamic)
|
|
144
|
+
(re-find (re-pattern "hello") "say hello!") ;; => "hello"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
;; Inline Regex Flags
|
|
149
|
+
;;
|
|
150
|
+
;; (?i) case-insensitive
|
|
151
|
+
;; (?m) multiline (^ and $ match line boundaries)
|
|
152
|
+
;; (?s) dotAll (. matches newlines too)
|
|
153
|
+
|
|
154
|
+
(comment
|
|
155
|
+
(re-find #"(?i)hello" "say HELLO!") ;; => "HELLO"
|
|
156
|
+
(re-matches #"(?i)[a-z]+" "HeLLo") ;; => "HeLLo"
|
|
157
|
+
|
|
158
|
+
(re-seq #"(?m)^\w+" "one\ntwo\nthree") ;; => ("one" "two" "three")
|
|
159
|
+
|
|
160
|
+
(re-seq #"(?im)^hello" "Hello\nHELLO\nhello")
|
|
161
|
+
;; => ("Hello" "HELLO" "hello")
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
;; Practical Patterns
|
|
166
|
+
|
|
167
|
+
(comment
|
|
168
|
+
;; Parse a CSV row
|
|
169
|
+
(defn parse-csv [line]
|
|
170
|
+
(str/split line #","))
|
|
171
|
+
|
|
172
|
+
(parse-csv "alice,30,admin") ;; => ["alice" "30" "admin"]
|
|
173
|
+
|
|
174
|
+
;; Extract structured data with groups
|
|
175
|
+
(defn parse-date [s]
|
|
176
|
+
(let [[_ y m d] (re-matches #"(\d{4})-(\d{2})-(\d{2})" s)]
|
|
177
|
+
{:year y :month m :day d}))
|
|
178
|
+
|
|
179
|
+
(parse-date "2024-03-15")
|
|
180
|
+
;; => {:year "2024" :month "03" :day "15"}
|
|
181
|
+
|
|
182
|
+
;; Slugify — URL-safe string
|
|
183
|
+
(defn slugify [s]
|
|
184
|
+
(-> s
|
|
185
|
+
str/trim
|
|
186
|
+
str/lower-case
|
|
187
|
+
(str/replace #"[^a-z0-9\s-]" "")
|
|
188
|
+
(str/replace #"\s+" "-")))
|
|
189
|
+
|
|
190
|
+
(slugify " Hello, World! It's Clojure ")
|
|
191
|
+
;; => "hello-world-its-clojure"
|
|
192
|
+
|
|
193
|
+
;; Template substitution — replace {{key}} placeholders
|
|
194
|
+
(defn render [template data]
|
|
195
|
+
(str/replace template
|
|
196
|
+
#"\{\{(\w+)\}\}"
|
|
197
|
+
(fn [[_ key]] (get data key ""))))
|
|
198
|
+
|
|
199
|
+
(render "Hello, {{name}}! You have {{count}} messages."
|
|
200
|
+
{"name" "Alice" "count" "3"})
|
|
201
|
+
;; => "Hello, Alice! You have 3 messages."
|
|
202
|
+
)
|