🚀 Clojure Introduction
🎯 Complete Definition
Clojure is a modern, dynamic, functional dialect of Lisp that runs on the Java Virtual Machine (JVM) and also compiles to JavaScript (ClojureScript). Created by Rich Hickey and first released in 2007, Clojure combines the interactive development and code-as-data philosophy of Lisp with the robustness and libraries of the JVM [citation:1]. It emphasizes immutability, persistent data structures, and a pragmatic approach to concurrency, making it ideal for complex, multi-threaded applications [citation:9].
🔬 Core Characteristics
- Functional: Functions are first-class, data is immutable, and programs are built by applying functions to data [citation:2].
- Lisp Dialect: Homoiconic (code is data), leveraging macros for powerful metaprogramming [citation:5].
- Immutable by Default: Core data structures (lists, vectors, maps, sets) are persistent and immutable, ensuring thread-safety [citation:3].
- Dynamic & Interactive: Developed with a REPL (Read-Eval-Print Loop) for interactive programming and live coding.
- JVM Hosted: Seamless Java interoperation, allowing use of any Java library [citation:7].
- Modern Concurrency: Software Transactional Memory (STM), agents, atoms, and core.async for managing state and concurrency [citation:9].
- Code as Data (Homoiconicity): The syntax is composed of Clojure data structures, enabling macros to transform code in a predictable way [citation:5].
📊 Industry Usage
Clojure is used by companies like Walmart, CircleCI, and Nubank for its stability, performance, and developer productivity. It's popular in finance, big data, and complex web applications. ClojureScript is widely adopted for front-end development, often with frameworks like Reagent and re-frame [citation:6].
📊 Syntax & Forms
🎯 Complete Definition
Clojure syntax consists of forms (expressions) that are evaluated to produce values. All code is written as lists of symbols and data. The fundamental unit is the form: numbers, strings, keywords, symbols, lists, vectors, maps, and sets. Lists are interpreted as calls (unless quoted).
🏗️ Basic Forms
- Numbers:
42,3.14,1/3(ratio),2N(big int) [citation:3]. - Strings:
"Hello"(Java String). - Characters:
\a,\newline. - Keywords:
:keyword(efficient identifiers, often used as map keys) [citation:3]. - Symbols:
my-symbol(name that refers to something else, like a function or var) [citation:3]. - Booleans:
true,false. Alsonilrepresents "no value" and is logically false [citation:3]. - Comments:
;;for line comments,(comment ...)for blocks.
📦 Data Types
🎯 Complete Definition
Clojure data types are rich and leverage Java's type system. It provides scalars (numbers, strings, keywords) and powerful collection types. Numbers include integers, floats, ratios, and arbitrary precision with BigInt (N) and BigDecimal (M) [citation:3].
🔤 Type Details
- Integers: Java
longby default. Arithmetic overflows throw exception; use+',*'for auto-promoting to BigInt [citation:3]. - Ratio: Exact fraction, e.g.,
(/ 1 3)yields1/3. - BigInt: Literal
123N. - BigDecimal: Literal
3.14159M. - Strings: Java
String; many Java methods available via interop [citation:3]. - Keywords: Interned, fast equality, used as functions for map lookup [citation:3].
- Symbols: Represent identifiers; also usable as functions for map lookup [citation:3].
- nil: The absence of value; equals Java
null.
⚙️ Functions
🎯 Complete Definition
Functions are first-class citizens. They are defined with defn (a macro that combines def and fn). Clojure supports arity overloading, variadic functions, anonymous functions with fn or reader literal #(), and closures [citation:2]. Functions automatically return the value of the last expression.
⚙️ Function Features
- Named:
(defn square [x] (* x x)). - Multi-arity: Different bodies based on argument count [citation:2].
- Variadic:
& restcollects extra args as a list [citation:2]. - Anonymous:
(fn [x] (* x x))or#(* % %)[citation:2]. - Higher-order: Functions that take or return functions (
map,filter,reduce). - Docstrings:
(defn square "Returns x squared" [x] (* x x)).
📋 Collections
🎯 Complete Definition
Clojure collections are immutable, persistent, and support efficient structural sharing. They implement interfaces for reading, and all are counted, iterable, and support seq [citation:3]. The four main types are lists, vectors, maps, and sets [citation:8].
📚 Collection Types
- List:
'(1 2 3)or(list 1 2 3). Singly linked, efficient for prepending (conjat front) [citation:8]. - Vector:
[1 2 3]. Indexed, efficient for appending (conjat end) and random access [citation:8]. - Map:
{:a 1, :b 2}. Key-value pairs, both hash-map and sorted-map [citation:3]. - Set:
#{:a :b :c}. Unordered collection of unique values [citation:3].
🔀 Control Flow
🎯 Complete Definition
Control flow in Clojure relies on expressions, not statements. Common constructs: if, if-let, when, cond, case, and for (list comprehension). There is no early return; functions always return the last value.
🏗️ Flow Constructs
- if:
(if test then-expr else-expr)(else is optional). - when:
(when test & body)– likeifwithout else, for side effects. - cond: Series of test/expr pairs, like switch.
- case: Dispatch on constant values (fast).
- for: List comprehension (not loop, returns lazy seq).
- do: Groups multiple expressions, returns last.
- if-let / when-let: Bind and test in one step.
🔗 Sequences
🎯 Complete Definition
The sequence abstraction is central to Clojure. The seq function returns a sequential view of any collection. Sequences are lazy (values computed only when needed) and can represent infinite streams. Most collection functions (map, filter, reduce) are sequence functions that return lazy seqs [citation:6].
🔧 Key Sequence Functions
- map:
(map inc [1 2 3])=>(2 3 4). - filter:
(filter even? [1 2 3 4])=>(2 4). - reduce:
(reduce + [1 2 3 4])=>10. - take/drop:
(take 3 (range))=>(0 1 2). - into: Converts seq back to collection.
- ->> and ->: Threading macros for readability [citation:6].
📝 Destructuring
🎯 Complete Definition
Destructuring allows you to bind names to parts of a data structure concisely. Works with vectors (positional) and maps (associative). It can be nested and can provide default values [citation:7]. Used in let, function parameters, and for bindings.
🔍 Destructuring Types
- Sequential (vector):
[a b c]binds first three elements. - Associative (map):
{:keys [name age]}bindsname,agefrom map with keywords;:as wholecaptures entire map. - Rest:
[x y & rest]captures remaining. - :or: Provides default values.
- Nesting: Combine forms arbitrarily.
📦 Namespaces
🎯 Complete Definition
Namespaces organize code and avoid name collisions. They are dynamic mappings from symbols to Vars (global variables). Use ns macro to define or switch namespace. Require, use, and import control access to other namespaces and Java classes [citation:7].
📦 Namespace Operations
- ns:
(ns my.project.core (:require [clojure.string :as str])) - require: Loads a namespace, optionally with alias.
- refer: Brings specific symbols into current ns.
- import: Imports Java classes.
- create-ns / in-ns: Lower-level functions.
- alias: Create alias for a namespace.
🔄 State & Identity
🎯 Complete Definition
State management in Clojure separates identity from state. Identities (references) are mutable references to immutable values. Over time, an identity can point to different immutable states. Clojure provides three reference types for managing changing state: Atoms, Refs, and Agents, each with different coordination semantics [citation:9].
🔄 Reference Types
- Atoms: Synchronous, independent, uncoordinated updates. Use
swap!(apply function) orreset![citation:4]. - Refs: Synchronous, coordinated updates via Software Transactional Memory (STM) with
dosync,alter,ref-set[citation:9]. - Agents: Asynchronous, independent updates. Send actions with
send(pooled threads) orsend-off. - Vars: Per-thread bindings with
binding[citation:9].
⚡ Concurrency
🎯 Complete Definition
Concurrency in Clojure is built on immutable data and managed references, making multi-threaded programming safer. In addition to STM, atoms, and agents, Clojure offers core.async for CSP-style concurrency with channels and go blocks [citation:7]. Futures and promises simplify async tasks.
⚡ Concurrency Primitives
- Future:
(future (long-running-task))– runs in another thread, returns a deref-able object. - Promise:
(def p (promise)) (deliver p value)– one-time delivery. - pmap: Parallel version of map.
- core.async: Channels,
goblocks,,>!!for async communication.
🔮 Macros
🎯 Complete Definition
Macros are compile-time functions that transform code. Because Clojure is homoiconic (code is data), macros receive unevaluated forms and return new forms to compile. They enable custom syntactic abstractions without changing the language [citation:5]. Use macros when functions cannot (e.g., need to control evaluation, create new binding forms).
🔮 Macro Essentials
- defmacro: Defines a macro.
- Syntax quote (`): Quotes a form, but allows unquoting (~) and unquote-splicing (~@).
- Unquote (~): Inside syntax quote, evaluates the expression.
- Unquote-splicing (~@): Inserts all elements of a list.
- gensym: Generates unique symbols to avoid naming conflicts.
☕ Java Interop
🎯 Complete Definition
Java interoperability is seamless in Clojure. You can create objects, call methods, access fields, extend classes, and implement interfaces using concise syntax. This gives Clojure access to the entire Java ecosystem [citation:7].
☕ Interop Syntax
- Instance creation:
(java.util.Date.)– dot after class name. - Instance method:
(.toUpperCase "hello")or(. "hello" toUpperCase). - Static method:
(Math/sqrt 25)– slash. - Static field:
Math/PI. - Instance field:
(.-x point)– dot dash. - Inner class:
java.util.Map$Entry. - Implementing interfaces:
proxyorreify[citation:7].
🚀 Advanced Features
🎯 Complete Definition
Advanced Clojure includes features like protocols, records, transducers, reducers, and spec. These enable high-performance abstraction, data validation, and generation [citation:7].
🚀 Advanced Topics
- Protocols: High-performance polymorphism (like interfaces) [citation:7].
- Records: Map-like with fixed keys, faster access [citation:3].
- Transducers: Composable algorithmic transformations independent of context (can be used with collections, channels, etc.) [citation:1].
- Spec: Specification for data and functions, with validation and generative testing.
- Reducers: Parallel fold operations on collections [citation:1].
- core.logic: Logic programming [citation:1].
- core.async: CSP channels.