-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclojure
More file actions
1915 lines (1373 loc) · 75.2 KB
/
clojure
File metadata and controls
1915 lines (1373 loc) · 75.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
description: Clojure
globs: "*.clj,*.cljs,*.cljc,*.edn"
alwaysApply: false
---
# Idiomatic Clojure: A Comprehensive Style Guide
## Introduction
This guide distills wisdom from foundational Clojure resources, including "The Joy of Clojure" by Michael Fogus and Chris Houser and "Elements of Clojure" by Zachary Tellman. It provides comprehensive guidelines for writing idiomatic, maintainable, and effective Clojure code.
Clojure's design emphasizes simplicity, immutability, and functional programming, enabling powerful abstractions that separate "what" from "how" and allowing developers to manage complexity through effective indirection. This guide will help you harness these strengths to produce robust, elegant Clojure code.
## 1. Clojure Philosophy and Fundamentals
### 1.1 Core Principles
#### Simplicity
Clojure embodies simplicity through its design and encourages developers to create simple, composable solutions.
- **Simplicity over complexity**: Clojure follows Rich Hickey's definition of simplicity as the opposite of complexity, not the opposite of easy. Simple means "one fold" or "one braid" - a unit that cannot be broken down further.
- **Build simple abstractions**: Create focused functions that do one thing well rather than monolithic functions that serve multiple purposes.
- **Compose simple parts**: Solve complex problems by combining simple, well-understood components.
- **Avoid incidental complexity**: Don't introduce complexity that isn't inherent to the problem domain.
```clojure
;; Simple, focused function
(defn celsius->fahrenheit [c]
(+ (* c (/ 9 5)) 32))
;; Complex, less focused function with side effects
(defn process-temperature [c]
(println "Processing temperature...")
(let [f (+ (* c (/ 9 5)) 32)]
(println "Result:" f)
(swap! temperature-log conj {:input c :output f :timestamp (java.util.Date.)})
f))
```
#### Immutability
Immutability is a cornerstone of Clojure's design philosophy, enabling safer code and simplifying reasoning about program behavior.
- **Data is immutable by default**: Clojure collections cannot be changed after creation.
- **Transformations create new data**: Functions like `assoc`, `conj`, and `update` return new collections rather than modifying existing ones.
- **Structural sharing preserves efficiency**: Clojure's persistent data structures efficiently reuse parts of existing collections when creating new ones.
- **Immutability enables reasoning**: It's easier to understand code when you know data cannot change unexpectedly.
- **Immutability supports concurrency**: Immutable data eliminates a whole class of concurrency bugs related to shared mutable state.
```clojure
;; Creating a map
(def person {:name "Alice" :age 30})
;; Transforming a map (creates a new map)
(def older-person (update person :age inc))
;; Original is unchanged
person ;=> {:name "Alice" :age 30}
older-person ;=> {:name "Alice" :age 31}
```
#### Functional Programming
Clojure is fundamentally a functional programming language that treats functions as first-class entities.
- **Pure functions**: Functions should produce outputs based solely on their inputs, without side effects.
- **First-class functions**: Functions can be passed as arguments, returned from other functions, and stored in data structures.
- **Higher-order functions**: Functions that take other functions as arguments or return functions.
- **Function composition**: Building complex behavior by combining simpler functions.
- **Declarative style**: Expressing what should be computed rather than how it should be computed.
- **Referential transparency**: A function call can be replaced with its result without changing the program's behavior.
- **Immutable data transformations**: Modeling computation as a series of transformations on immutable data.
```clojure
;; Pure function
(defn multiply [a b]
(* a b))
;; Higher-order function
(defn apply-twice [f x]
(f (f x)))
;; Function composition
(def double-then-increment (comp inc #(* 2 %)))
(double-then-increment 3) ;=> 7
```
#### Data-Oriented Programming
Clojure emphasizes working with data structures directly rather than wrapping them in objects with behavior.
- **Data as the central abstraction**: Focus on the data and its transformations.
- **Generic data manipulation**: Use the same functions across different data types.
- **Data-first design**: Design your system around the core data structures.
- **Separate data from operations**: Keep data pure and apply transformations through functions.
- **Leverage data literals**: Use Clojure's literal syntax for maps, vectors, sets, etc.
- **Extensible operations via multimethods and protocols**: Add behavior without changing the data.
```clojure
;; Data-oriented approach
(def users [{:id 1 :name "Alice" :email "alice@example.com"}
{:id 2 :name "Bob" :email "bob@example.com"}])
;; Functions operating on data
(defn find-user-by-id [users id]
(first (filter #(= id (:id %)) users)))
(defn user-emails [users]
(map :email users))
```
#### REPL-Driven Development
The REPL (Read-Eval-Print Loop) is fundamental to Clojure development, enabling an interactive, incremental approach.
- **Incremental development**: Build and test small pieces of functionality at a time.
- **Immediate feedback**: See the results of code changes instantly.
- **Exploration**: Experiment with ideas and approaches in real-time.
- **Interactive debugging**: Test hypotheses and fix issues directly in the REPL.
- **Live system interaction**: Connect to running systems to inspect and modify their behavior.
- **Documentation exploration**: Look up function signatures and documentation interactively.
- **Iterative design**: Evolve designs based on real interactions with the code.
```clojure
;; REPL interaction example
user=> (def numbers [1 2 3 4 5])
#'user/numbers
user=> (map inc numbers)
(2 3 4 5 6)
user=> (filter even? numbers)
(2 4)
user=> (reduce + numbers)
15
user=> (doc map)
;; Displays documentation for the map function
```
#### Homoiconicity
Clojure's "code as data" property enables powerful metaprogramming through macros.
- **Code represented as data structures**: Clojure code is composed of lists, vectors, maps, etc.
- **Ability to manipulate code as data**: Transform code at compile time.
- **Macros for syntactic abstraction**: Extend the language with new constructs.
- **Reader and evaluation separation**: The reader converts text to data structures, which are then evaluated.
- **Quoting and unquoting**: Tools for template-based code generation.
- **Code walking**: Traverse and transform code structures.
```clojure
;; Code as data
'(+ 1 2 3) ;=> a list containing the symbol + and numbers 1, 2, 3
;; A simple macro
(defmacro when-valid [value pred & body]
`(let [v# ~value]
(when (~pred v#)
~@body)))
(when-valid "test" string?
(println "Valid string!")
(count "test"))
```
#### Sequence Abstraction
Clojure provides a unified approach to collection processing through its sequence abstraction.
- **Unified collection interface**: Common functions work across different collection types.
- **Lazy evaluation**: Compute elements only when needed.
- **Infinite sequences**: Represent conceptually infinite series without exhausting memory.
- **Sequence transformations**: Process collections through map, filter, reduce, etc.
- **Chunked sequences**: Optimize performance by processing items in groups.
- **Transducers**: Composable, efficient transformation operations independent of the collection type.
```clojure
;; Works on lists, vectors, sets, maps, strings, etc.
(map inc [1 2 3]) ;=> (2 3 4)
(map inc '(1 2 3)) ;=> (2 3 4)
(map inc #{1 2 3}) ;=> (2 3 4)
(map (fn [[k v]] [k (inc v)]) {:a 1 :b 2}) ;=> ([:a 2] [:b 3])
;; Infinite sequence
(def natural-numbers (iterate inc 1))
(take 5 natural-numbers) ;=> (1 2 3 4 5)
;; Transducers
(def xform (comp (filter even?) (map inc)))
(transduce xform + (range 10)) ;=> 25
```
### 1.2 Additional Fundamental Principles
- **Explicitness over implicitness**: Make the intent of your code clear rather than relying on hidden conventions or implicit behaviors.
- **Value reader understanding**: Write code that can be understood layer by layer, where each layer reveals meaningful abstraction rather than incidental details.
- **Separation of concerns**: Distinguish between operational concerns (how and when code executes) and functional concerns (what the code actually computes).
- **Progressive disclosure**: Structure code so that high-level patterns are immediately visible, with details accessible only when needed.
- **Adaptability over excessive anticipation**: Build systems that can adapt to change rather than trying to anticipate every possible requirement upfront.
- **Inductive models over deductive ones**: Prefer models that work by analogy and comparison (inductive) rather than attempting to predict all possible states (deductive) when interfacing with the real world.
- **Pragmatism**: Balance functional purity with practical considerations, especially regarding interoperability with host platforms.
## 2. Code Style and Organization
### 2.1 Naming Conventions
#### General Naming Principles
1. **Names should be narrow and consistent**: A narrow name clearly excludes things it cannot represent. A consistent name is easily understood by someone familiar with the surrounding code, the problem domain, and the broader Clojure ecosystem.
2. **Balance natural and synthetic names**: Natural names (like "student") connect to intuitive understanding but can carry multiple senses. Synthetic names (like "monad") have no intuitive meaning but can be precisely defined. Use natural names at the system boundaries and for high-level concepts; use synthetic names for technical abstractions with no real-world analog.
3. **Consider the audience**: Names at the topmost layers of code will be read by novices and experts alike and should be chosen accordingly. Lower layers may assume more familiarity with the domain.
4. **Names create indirection**: A good name separates what something does from how it does it. When naming, consider whether you're trying to hide implementation details (good) or expose them (usually bad).
5. **Avoid overly specific names**: Names that expose implementation details make code harder to change. For example, `uuid-generator` is too specific compared to `id-generator`.
6. **Avoid overly general names**: Names that are too general obscure the fundamental properties of what they represent. For example, `data-processor` could mean almost anything.
7. **Match naming to module scope**: Names should become more generic as the scope of a module narrows. A function in a `student` namespace doesn't need to include "student" in every function name.
#### Naming Data
1. **Default conventions for generic parameters**:
- Use `x` for values that could be anything
- Use `xs` for sequences of anything
- Use `m` for maps (not `map` which shadows a core function)
- Use `f` for functions
- Use `k` for keys (not `key` which shadows a core function)
- Use `v` for values
- Use `n` for numbers
- Use `this` for self-references in protocols, deftypes, and anonymous functions
2. **Document generic parameters**: If a parameter can accept many types, document what operations will be performed on it so callers understand the constraints.
3. **Name maps according to their key-value relationship**: Use the convention `key->value` for maps, e.g., `student->grade` for a map of students to grades.
4. **Name tuples according to their contents**: Use the convention `a+b` for tuples, e.g., `student+teacher` for a tuple containing a student and teacher.
5. **Name sequences according to what they contain**: A sequence of students should be called `students`, not `student-seq` or `student-list`.
6. **Consider let-bindings for clarity**: When right-side expressions are complex, use let-bindings with meaningful names to clarify intent without needing to understand implementation.
7. **Be explicit about value absence**: Rather than relying on `nil` with its many possible interpretations, use explicit values like keyword markers (`:not-found`, `:no-student`) to indicate absence.
#### Naming Functions
1. **Function names should indicate scope crossing**: If a function crosses data scope boundaries (pulling data from or pushing to another scope), there should be a verb in the name (e.g., `get-student`, `save-record!`). `get` implies local access, whereas `fetch` implies remote fetching, which may require control flow & retries.
2. **Pure transformation functions can omit verbs**: Functions that only transform data within the same scope can often use nouns or adjectives (`sorted`, `md5`, `uppercase`).
3. **Be explicit about effectful functions**: For functions with side effects, suffix the name with `!`, e.g. `save!`, `delete!`, `update-atom!`.
4. **Match function name to namespace context**: In a namespace specific to a datatype, functions can have shorter names. In `student` namespace, `get` is fine instead of `get-student`.
5. **Specify exclusions when shadowing core functions**: If you must define a function that shadows a core function, use `:refer-clojure :exclude [get]` at the top of your namespace.
6. **Name predicates with ?**: Functions that return boolean values should end with a question mark, e.g., `valid?`, `student?`.
7. **Name conversion functions with ->**: Functions that convert between types should use the arrow syntax, e.g., `->int`, `->string`.
8. **Name higher-order functions after their behavior**: Functions that take or return functions should be named according to what they do with those functions, e.g., `memoize`, `complement`, `partial`.
#### Naming Macros
1. **Macros should communicate their nature**: Names should indicate that they are macros and prompt readers to look at their implementation.
2. **Use with- prefix for context macros**: Macros that establish a context or binding should use the `with-` prefix, e.g., `with-open`, `with-redefs`.
3. **Document the expansion**: For syntactic macros, document the expansion pattern so readers can understand what code is actually generated.
### 2.2 Namespace Organization
1. **One namespace per file**: Follow the convention of one namespace per file.
2. **Namespace naming**: Use reverse-domain notation (e.g., `com.example.project.module`).
3. **File organization**: Place files in a directory structure matching the namespace hierarchy.
4. **Small, focused namespaces**: Keep namespaces small and focused on a single responsibility.
5. **Require and import**: Use `:require` and `:import` to include other namespaces.
6. **Aliasing**: Use `:as` to create short aliases for long namespace names.
7. **Referring**: Use `:refer` selectively for commonly used functions.
```clojure
;; Defining a namespace
(ns com.example.myapp.core
"Core functionality for the myapp application"
(:require [clojure.string :as string] ; use :as string to distinguish from clojure.core/str.
[clojure.set :refer [union intersection]]
[com.example.myapp.util :as util])
(:import [java.io File FileReader]
[java.util Date]))
;; Private function (not accessible outside the namespace)
(defn- internal-helper [x]
(string/upper-case x))
;; Public function (accessible from other namespaces)
(defn public-api [x]
(str "Result: " (internal-helper x)))
```
### 2.3 Documentation Practices
1. **Write clear, concise docstrings**: Document what functions do, their parameters, and return values.
2. **Document the why, not just the what**: Explain why certain decisions were made, not just what the code does.
3. **Keep documentation close to the code**: Docstrings and comments are more likely to stay accurate than separate documentation.
4. **Use consistent documentation style**: Follow consistent patterns in your docstrings.
5. **Document assumptions**: Make implicit assumptions explicit in your documentation.
6. **Document side effects**: Clearly indicate when functions have side effects.
7. **Provide examples in docstrings**: Examples help clarify how functions should be used.
8. **Document performance characteristics**: When performance is relevant, document expected performance.
9. **Keep docstrings up to date**: Update documentation when code changes.
10. **Consider generated documentation**: Tools like codox can generate documentation from your code.
## 3. Data and Collections
### 3.1 Core Data Structures
#### Vectors
Vectors are indexed sequential collections with efficient random access, append operations, and sub-vector creation.
- **Use for**: Sequential indexed data, accumulating items, representing records, function arguments.
- **Fast operations**: Random access by index, appending to the end, updating by index.
- **Literal syntax**: `[1 2 3]`
- **Core functions**: `conj`, `assoc`, `get`, `update`, `nth`, `subvec`
```clojure
;; Creating vectors
(def v [1 2 3 4 5])
(vector 1 2 3 4 5)
(vec (range 1 6))
;; Accessing elements
(get v 2) ;=> 3
(nth v 2) ;=> 3
(v 2) ;=> 3 (vectors are functions of their indices)
;; Adding elements (to the end)
(conj v 6) ;=> [1 2 3 4 5 6]
;; Updating elements
(assoc v 2 10) ;=> [1 2 10 4 5]
;; Vector as a stack
(peek v) ;=> 5 (last element)
(pop v) ;=> [1 2 3 4] (removes last element)
```
#### Lists
Lists are sequential collections with efficient first/rest operations, optimized for processing from front to back.
- **Use for**: Representing code, sequences processed from beginning to end, stacks.
- **Fast operations**: Adding to the front, accessing the first element, recursive processing.
- **Literal syntax**: `'(1 2 3)` or `(list 1 2 3)`
- **Core functions**: `first`, `rest`, `next`, `cons`, `conj`
```clojure
;; Creating lists
(def l '(1 2 3 4 5))
(list 1 2 3 4 5)
;; Accessing elements
(first l) ;=> 1
(rest l) ;=> (2 3 4 5)
(nth l 2) ;=> 3 (slower than with vectors)
;; Adding elements (to the front)
(conj l 0) ;=> (0 1 2 3 4 5)
(cons 0 l) ;=> (0 1 2 3 4 5)
;; List as a stack
(peek l) ;=> 1 (first element)
(pop l) ;=> (2 3 4 5) (removes first element)
```
#### Maps
Maps represent key-value associations with efficient lookup by key.
- **Use for**: Key-value relationships, lookup tables, dictionaries, domain entities, configuration.
- **Fast operations**: Lookup by key, adding/updating entries.
- **Literal syntax**: `{:a 1 :b 2}`
- **Types**: hash-map (default), sorted-map, array-map (preserves order for small maps)
- **Core functions**: `get`, `assoc`, `dissoc`, `select-keys`, `merge`, `update`
```clojure
;; Creating maps
(def m {:name "Alice" :age 30 :city "Wonderland"})
(hash-map :name "Alice" :age 30 :city "Wonderland")
(array-map :name "Alice" :age 30 :city "Wonderland") ;; preserves insertion order
(sorted-map :name "Alice" :age 30 :city "Wonderland") ;; sorts by key
;; Accessing values
(get m :name) ;=> "Alice"
(:name m) ;=> "Alice" (keywords are functions)
(m :name) ;=> "Alice" (maps are functions of their keys)
(get-in m [:address :zipcode] "default") ;=> "default" (nested lookup with default)
;; Adding/updating entries
(assoc m :email "alice@wonderland.com") ;=> {:name "Alice" :age 30 :city "Wonderland" :email "alice@wonderland.com"}
(update m :age inc) ;=> {:name "Alice" :age 31 :city "Wonderland"}
(merge m {:email "alice@wonderland.com" :phone "123-456"}) ;=> {:name "Alice" :age 30 :city "Wonderland" :email "alice@wonderland.com" :phone "123-456"}
;; Removing entries
(dissoc m :city) ;=> {:name "Alice" :age 30}
(select-keys m [:name :age]) ;=> {:name "Alice" :age 30}
```
#### Sets
Sets are collections of unique values with efficient membership testing.
- **Use for**: Unique collections, membership testing, de-duplication.
- **Fast operations**: Testing if an element is in the set, adding/removing elements.
- **Literal syntax**: `#{1 2 3}`
- **Types**: hash-set (default), sorted-set
- **Core functions**: `conj`, `disj`, `contains?`, `union`, `intersection`, `difference`
```clojure
;; Creating sets
(def s #{1 2 3 4 5})
(hash-set 1 2 3 4 5)
(set [1 2 3 3 4 5 5]) ;=> #{1 2 3 4 5} (removes duplicates)
;; Testing membership
(contains? s 3) ;=> true
(s 3) ;=> 3 (sets are functions of their elements)
(s 10) ;=> nil
;; Adding/removing elements
(conj s 6) ;=> #{1 2 3 4 5 6}
(disj s 1) ;=> #{2 3 4 5}
;; Set operations
(clojure.set/union s #{4 5 6 7}) ;=> #{1 2 3 4 5 6 7}
(clojure.set/intersection s #{4 5 6 7}) ;=> #{4 5}
(clojure.set/difference s #{4 5 6 7}) ;=> #{1 2 3}
```
#### Queues
Persistent queues provide efficient FIFO (first-in-first-out) operations.
- **Use for**: Processing items in order of arrival, job queues, breadth-first algorithms.
- **Fast operations**: Adding to the rear, removing from the front.
- **Core functions**: `conj`, `peek`, `pop`
- **Note**: Implemented in `clojure.lang.PersistentQueue`
```clojure
;; Creating a queue
(def q (conj clojure.lang.PersistentQueue/EMPTY 1 2 3))
;; Adding to the queue (at the end)
(def q2 (conj q 4 5))
;; Examining the front of the queue
(peek q2) ;=> 1
;; Removing from the front
(pop q2) ;=> a queue containing 2, 3, 4, 5
```
### 3.2 Leveraging Persistence and Structural Sharing
Clojure's persistent data structures maintain their performance through clever structural sharing.
- **Structural sharing**: When creating a "modified" version of a collection, Clojure reuses as much of the original structure as possible.
- **Path copying**: Only the path to the changed elements is copied, not the entire structure.
- **Performance implications**: Most operations are O(log n) rather than O(n).
- **Memory efficiency**: Multiple versions of data structures share common elements.
- **Persistent vs. transient**: Use transient collections in performance-critical loops for localized mutability.
```clojure
;; Example of structural sharing
(def original (vec (range 1000)))
(def modified (assoc original 500 :x))
;; original and modified share most of their structure
;; only the path to index 500 is copied
;; Example of transient collections for performance
(defn fast-map-invert [m]
(persistent!
(reduce-kv (fn [m k v] (assoc! m v k))
(transient {})
m)))
```
### 3.3 Collection Functions and Transformations
Clojure provides a rich set of functions for working with collections in a functional manner.
#### Sequence Functions
- **`map`**: Transform each element with a function
- **`filter`**: Select elements that satisfy a predicate
- **`reduce`**: Combine all elements using a function
- **`partition`**: Split into groups of specified size
- **`group-by`**: Group elements by a function's result
- **`frequencies`**: Count occurrences of each distinct item
- **`keep`**: Like map, but removes nil results
```clojure
;; Examples of sequence functions
(map inc [1 2 3 4]) ;=> (2 3 4 5)
(filter even? [1 2 3 4]) ;=> (2 4)
(reduce + [1 2 3 4]) ;=> 10
(partition 2 [1 2 3 4 5 6]) ;=> ((1 2) (3 4) (5 6))
(group-by count ["a" "ab" "abc"]) ;=> {"a" 1, "ab" 2, "abc" 3}
(frequencies [1 1 2 3 2 1]) ;=> {1 3, 2 2, 3 1}
(keep #(when (even? %) (* % %)) [1 2 3 4]) ;=> (4 16)
```
#### Transformations
- **`update`**: Apply a function to a value in a collection
- **`update-in`**: Apply a function to a value in a nested collection
- **`assoc-in`**: Set a value in a nested collection
- **`get-in`**: Get a value from a nested collection
- **`dissoc-in`**: Remove a key-value pair from a nested collection
```clojure
;; Examples of transformation functions
(def person {:name "Alice" :address {:city "Wonderland" :street "Rabbit Hole"}})
(update person :name clojure.string/upper-case)
;=> {:name "ALICE" :address {:city "Wonderland" :street "Rabbit Hole"}}
(update-in person [:address :city] clojure.string/upper-case)
;=> {:name "Alice" :address {:city "WONDERLAND" :street "Rabbit Hole"}}
(assoc-in person [:address :zipcode] "12345")
;=> {:name "Alice" :address {:city "Wonderland" :street "Rabbit Hole" :zipcode "12345"}}
(get-in person [:address :city])
;=> "Wonderland"
```
#### Destructuring
Destructuring allows you to extract values from collections in a concise, declarative way.
- **Sequential destructuring**: Extract elements by position from vectors or sequences
- **Associative destructuring**: Extract elements by key from maps
- **Nested destructuring**: Combine both forms for complex data structures
- **Default values**: Provide fallbacks for missing values
- **Rest parameters**: Capture remaining elements
```clojure
;; Sequential destructuring
(let [[first second & rest] [1 2 3 4 5]]
[first second rest]) ;=> [1 2 (3 4 5)]
;; Associative destructuring
(let [{name :name age :age} {:name "Alice" :age 30 :city "Wonderland"}]
[name age]) ;=> ["Alice" 30]
;; Shorthand with :keys
(let [{:keys [name age]} {:name "Alice" :age 30}]
[name age]) ;=> ["Alice" 30]
;; Nested destructuring
(let [{:keys [name] {:keys [city]} :address}
{:name "Alice" :address {:city "Wonderland" :street "Rabbit Hole"}}]
[name city]) ;=> ["Alice" "Wonderland"]
;; Default values
(let [{:keys [name age] :or {age 25}} {:name "Alice"}]
[name age]) ;=> ["Alice" 25]
;; Function parameters
(defn print-user [{:keys [name age] :or {age "unknown"}}]
(println name "is" age "years old"))
(print-user {:name "Alice" :age 30}) ;=> Alice is 30 years old
(print-user {:name "Bob"}) ;=> Bob is unknown years old
```
### 3.4 Handling Nil and Absence
Proper nil handling is essential in Clojure to avoid unexpected errors and write robust code.
- **The "nil punning" problem**: Using nil to represent both "nothing" and "false" can lead to confusion.
- **Explicit nil checks**: Use `nil?` to explicitly test for nil when needed.
- **Nil-safe operations**: Use `some`, `some->`, and `some->>` for nil-safe operations.
- **Default values**: Use functions like `or`, `get` with defaults, and destructuring defaults.
- **Boolean interpretation**: Be aware that nil and false are the only falsy values in Clojure.
- **Interpret nil at regular intervals**: Don't allow ambiguity about what nil means to propagate through your code. Interpret nil into a more specific value.
- **Consider alternatives to nil**:
- Keywords (`:not-found`, `:missing`) are more explicit
- Empty collections (`[]`, `{}`, `#{}`) are often better than nil for representing "nothing"
- Default values that preserve type information
- Maybe monads for functional composition with nil handling
```clojure
;; Explicit nil checks
(when-not (nil? x) (do-something-with x))
;; nil-safe operations
(some-> person :address :city clojure.string/upper-case)
;; returns nil if any step returns nil, otherwise applies all functions
;; Default values
(or nil-value "default") ;=> "default"
(get map-value :key "default") ;=> "default" if :key doesn't exist
;; Testing boolean conditions
(if x "x exists and is not false" "x is either nil or false")
```
### 3.5 Idiomatic Collection Usage
1. **Use the narrowest possible data accessor**: This communicates your intent clearly:
- `get` for maps
- `nth` for vectors or sequences
- `first`, `second` for accessing elements by position
- `contains?` to check set membership or map keys
- Use keywords or functions as accessors when appropriate (`(:key map)`, `(map :key coll)`)
2. **Combine collections appropriately**:
- `merge` for combining maps
- `concat` for joining sequences
- `into` for adding elements from one collection to another
- `conj` for adding single elements
3. **Transform collections with the right functions**:
- `map` for transforming each element
- `filter`/`remove` for selecting elements
- `reduce` for combining elements
- `group-by`/`partition-by` for splitting collections
4. **Understand lazy vs. realized collections**: Be aware of when collections are lazy (like `map` results) vs. fully realized (like `mapv` results).
5. **Consider performance characteristics**:
- Vectors provide O(1) access by index and efficient append
- Maps and sets provide O(log32 n) lookup
- Lists provide O(1) prepend but O(n) access by index
- Sequences may be lazy, realizing elements only as needed
## 4. Functional Programming Patterns
### 4.1 Pure Functions
Pure functions are the building blocks of functional programming in Clojure.
- **Deterministic**: Same inputs always produce the same outputs
- **No side effects**: Don't modify external state, perform I/O, etc.
- **Benefits**: Easier to test, reason about, compose, and parallelize
- **Referential transparency**: A function call can be replaced with its result
- **Separate pure logic from effects**: Keep core logic pure, handle effects at the boundaries
```clojure
;; Pure function
(defn factorial [n]
(if (<= n 1)
1
(* n (factorial (dec n)))))
;; Impure function (has side effects)
(defn log-and-factorial [n]
(println "Computing factorial of" n)
(let [result (factorial n)]
(println "Result is" result)
result))
;; Better: separate pure logic from effects
(defn factorial [n]
(if (<= n 1)
1
(* n (factorial (dec n)))))
(defn with-logging [f x]
(println "Computing with input" x)
(let [result (f x)]
(println "Result is" result)
result))
(with-logging factorial 5)
```
### 4.2 Function Design
1. **Functions should do one thing well**: Each function should have a single, well-defined purpose.
2. **Every function should represent a single action**: A function should pull data in, transform data, or push data out - preferably just one of these.
3. **Separate pure functions from effectful ones**: Keep functions that perform side effects separate from those that simply transform data.
4. **Minimize function arity**: Functions with many parameters are harder to understand and use correctly. Consider using maps for complex parameter sets.
5. **Functions should have predictable behavior**: The same inputs should always produce the same outputs, unless the function is explicitly side-effectful.
6. **Document function contracts clearly**: Use docstrings to communicate what a function does, what inputs it expects, and what outputs it produces.
7. **Make functions as general as appropriate, but no more**: Functions should be applicable to all the cases they might reasonably handle, but overgeneralization makes code harder to understand.
8. **Use pre/post conditions for complex invariants**: For functions with complex requirements or guarantees, use pre and post conditions to document and enforce them.
9. **Design functions for composition**: Functions that input and output similar types of data are easier to compose with other functions.
10. **Balance abstraction and concreteness**: Too abstract makes code hard to understand; too concrete makes it inflexible. Find the right balance for your context.
### 4.3 Higher-Order Functions and Composition
Higher-order functions take other functions as arguments or return them as results, enabling powerful abstractions.
#### Core Higher-Order Functions
- **`map`**: Apply a function to each element in a collection
- **`filter`**: Select elements that satisfy a predicate
- **`reduce`**: Combine elements using a binary function
- **`apply`**: Call a function with arguments from a sequence
- **`comp`**: Compose functions (right to left)
- **`partial`**: Partially apply a function (fix some arguments)
- **`juxt`**: Create a function that applies multiple functions to the same arguments
```clojure
;; map examples
(map inc [1 2 3]) ;=> (2 3 4)
(map #(* % %) [1 2 3]) ;=> (1 4 9)
(map + [1 2 3] [4 5 6]) ;=> (5 7 9)
;; filter example
(filter even? (range 10)) ;=> (0 2 4 6 8)
;; reduce examples
(reduce + [1 2 3 4]) ;=> 10
(reduce (fn [m [k v]] (assoc m k v)) {} [[:a 1] [:b 2]]) ;=> {:a 1 :b 2}
;; apply example
(apply + [1 2 3 4]) ;=> 10 (same as (+ 1 2 3 4))
;; comp example
(def neg-sum (comp - +))
(neg-sum 1 2 3) ;=> -6 (same as (- (+ 1 2 3)))
;; partial example
(def add5 (partial + 5))
(add5 10) ;=> 15
;; juxt example
(def stats (juxt count min max))
(stats [5 3 8 1 2]) ;=> [5 1 8]
```
#### Function Composition
Combining simple functions to create more complex ones is a fundamental technique in functional programming.
- **Use `comp` for right-to-left composition**: `(comp f g h)` is equivalent to `(fn [x] (f (g (h x))))`
- **Use threading macros for left-to-right composition**:
- `->` threads values as the first argument
- `->>` threads values as the last argument
- `as->` threads values with explicit binding
- **Use `juxt` to apply multiple functions to the same input**
- **Build complex transformations from simple functions**
```clojure
;; Direct composition
(def negative-square (comp - #(* % %)))
(negative-square 5) ;=> -25
;; Using -> (thread-first)
(-> 5
(* 2) ;; becomes (* 5 2)
(+ 3) ;; becomes (+ (* 5 2) 3)
(/ 2)) ;=> 6.5
;; Using ->> (thread-last)
(->> [1 2 3 4]
(map inc) ;; becomes (map inc [1 2 3 4])
(filter even?) ;; becomes (filter even? (map inc [1 2 3 4]))
(reduce *)) ;=> 48
;; Using as-> for mixed position arguments
(as-> [1 2 3 4] $x
(map inc $x) ;; x is last arg (collection)
(nth $x 2) ;; x is first arg (collection)
(* $x $x)) ;=> 16
```
Functions that operate on a single argument `x` typically accept `x` as the first argument for convenient `->` threading, whereas functions that operate on a collection typically take `coll` as the last argument to work with `->>`. Specter follows this convention, taking `coll` as last argument.
### 4.4 Recursion and Iteration
Recursion is fundamental to functional programming, but Clojure offers several patterns to make it safer and more efficient.
#### Tail Recursion with `recur`
- **`recur` targets the nearest enclosing loop or function**
- **Eliminates stack overflow concerns**
- **Must be in tail position**
- **Often combined with accumulators**
```clojure
;; Non-tail-recursive factorial (inefficient for large inputs)
(defn factorial-bad [n]
(if (<= n 1)
1
(* n (factorial-bad (dec n)))))
;; Tail-recursive factorial with accumulator
(defn factorial [n]
(loop [n n
acc 1]
(if (<= n 1)
acc
(recur (dec n) (* acc n)))))
(factorial 5) ;=> 120
```
#### Sequence Operations Instead of Explicit Recursion
- **Prefer sequence operations (`map`, `filter`, etc.) over explicit recursion**
- **Use `for` comprehensions for nested iteration**
- **Leverage `iterate`, `repeat`, and other sequence generators**
```clojure
;; Instead of recursive looping, use sequence functions
(defn sum-of-squares [numbers]
(reduce + (map #(* % %) numbers)))
(sum-of-squares [1 2 3 4]) ;=> 30
;; For comprehensions instead of nested loops
(for [x (range 3)
y (range 3)
:when (not= x y)]
[x y])
;=> ([0 1] [0 2] [1 0] [1 2] [2 0] [2 1])
```
#### Recursive Problem Solving Patterns
- **Divide and conquer**: Split the problem, solve subproblems, combine results
- **Accumulation**: Build up the solution incrementally
- **Structural recursion**: Process nested data structures recursively
- **Mutual recursion**: Functions that call each other
```clojure
;; Divide and conquer: quicksort
(defn quicksort [coll]
(if (empty? coll)
[]
(let [pivot (first coll)
rest-coll (rest coll)
smaller (filter #(< % pivot) rest-coll)
larger (filter #(>= % pivot) rest-coll)]
(concat (quicksort smaller) [pivot] (quicksort larger)))))
;; Structural recursion: tree walking
(defn tree-depth [tree]
(if (or (nil? tree) (not (map? tree)))
0
(inc (apply max 0 (map tree-depth (vals (:children tree)))))))
```
### 4.5 Closures and State
Closures capture their lexical environment, allowing for state management within functional code.
- **Closures capture variables from their enclosing scope**
- **Use closures to create stateful functions without mutable variables**
- **Create factory functions that return specialized functions**
- **Implement memoization and caching patterns**
- **Balance pure functions with stateful closures when appropriate**
```clojure
;; Simple closure
(defn make-counter []
(let [!count (atom 0)]
(fn []
(swap! !count inc))))
(def next-count! (make-counter))
(next-count!) ;=> 1
(next-count!) ;=> 2
;; Closure for memoization
(defn memoize-fn [f]
(let [!cache (atom {})]
(fn [& args]
(let [key (apply list args)]
(if-let [cached-result (get @!cache key)]
cached-result
(let [result (apply f args)]
(swap! !cache assoc key result)
result))))))
(def memo-factorial (memoize-fn factorial))
```
### 4.6 Function Parameters and Return Values
1. **If a function accumulates values, support every arity**:
- 0-arity should return an appropriate identity value
- 1-arity should return the input unchanged
- 2-arity should combine the two inputs
- Variadic arity should reduce over all inputs
2. **Use option maps for complex parameters**: Rather than multiple positional parameters, use a map with keyword keys for functions with many optional parameters:
```clojure
;; Instead of this:
(defn search [query limit offset sort-by sort-order]...)
;; Do this:
(defn search [query {:keys [limit offset sort-by sort-order]
:or {limit 10, offset 0, sort-by :relevance, sort-order :desc}}]...)
```
3. **Return consistent types**: Functions should return similar types of data for similar inputs, making them more predictable and composable.
4. **Use meaningful return values from all branches**: Ensure every conditional branch in your function returns a meaningful value, not just some paths.
5. **Pass data structures, not multiple values**: When functions need to return multiple values, return them in a data structure rather than using multiple return values.
6. **Design for threading**: Functions that take their primary data structure as their first argument work well with `->`, while those that take it as their last argument work well with `->>`.
7. **Be consistent with predicate returns**: Predicate functions should always return true or false, not truthy or falsey values.
8. **Consider destructuring parameters**: Use Clojure's destructuring in function parameters to make your intent clearer:
```clojure
;; Instead of this:
(defn process-user [user]
(let [name (:name user)
email (:email user)]
...))
;; Do this:
(defn process-user [{:keys [name email]}]
...)
```
## 5. State Management
### 5.1 General State Principles
1. **Prefer values over state**: Use immutable data and pure functions whenever possible. Introduce state only when necessary.
2. **Isolate and minimize state**: Keep stateful parts of your system isolated and as small as possible.
3. **Make state changes explicit**: State changes should be clearly visible in your code, not hidden side effects.
4. **Choose the right state mechanism**:
- Atoms for independent values
- Refs for coordinated changes within a transaction
- Agents for asynchronous updates
- Vars for thread-local dynamic binding
5. **Use explicit do blocks to signal side effects**: When side effects are necessary, make them obvious through syntax.
6. **Initialize state with meaningful defaults**: Provide sensible initial values that represent a valid state.
7. **Consider component lifecycle management**: For complex stateful systems, use frameworks like Component or Integrant to manage lifecycle.
8. **Validate state transitions**: Ensure that state changes maintain your system invariants.
9. **Design for concurrent access**: Assume your state will be accessed by multiple threads and design accordingly.
10. **Distinguish between identity and state**: In Clojure, an identity (atom, ref, etc.) persists while its state (the value it refers to) changes over time. Prefix stateful values with `!` to distinguish from values, so that deref via `@!state` looks right.
### 5.2 Reference Types
Clojure provides several reference types to manage identity and state over time in a safe, coordinated manner.
#### Atoms
Atoms are stateful 'boxes' that contain individual values with thread-safe semantics to get the latest value or mutate state.
- **Use for**: Independent references that don't need coordination with other references
- **Operations**: `deref`/`@`, `reset!`, `swap!`, `compare-and-set!`
- **Thread safety**: Atomic updates via compare-and-swap (CAS) operations
- **Watcher functions**: Register watchers to monitor changes with `add-watch`
```clojure
;; Creating an atom
(def !counter (atom 0))