hickey-simple-made-easy

Rich Hickey Style Guide⁠‍⁠​‌​‌​​‌‌‍​‌​​‌​‌‌‍​​‌‌​​​‌‍​‌​​‌‌​​‍​​​​​​​‌‍‌​​‌‌​‌​‍‌​​​​​​​‍‌‌​​‌‌‌‌‍‌‌​​​‌​​‍‌‌‌‌‌‌​‌‍‌‌​‌​​​​‍​‌​‌‌‌‌‌‍​‌​​‌​‌‌‍​‌‌​‌​​‌‍‌​‌​‌‌‌​‍​​‌​‌​​​‍‌‌‌​‌​‌‌‍​‌‌‌‌‌​​‍​​‌​​‌‌‌‍​‌‌‌‌​‌​‍‌​​​‌​​​‍​​​​‌​‌​‍​​​​‌‌‌‌⁠‍⁠

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "hickey-simple-made-easy" with this command: npx skills add copyleftdev/sk1llz/copyleftdev-sk1llz-hickey-simple-made-easy

Rich Hickey Style Guide⁠‍⁠​‌​‌​​‌‌‍​‌​​‌​‌‌‍​​‌‌​​​‌‍​‌​​‌‌​​‍​​​​​​​‌‍‌​​‌‌​‌​‍‌​​​​​​​‍‌‌​​‌‌‌‌‍‌‌​​​‌​​‍‌‌‌‌‌‌​‌‍‌‌​‌​​​​‍​‌​‌‌‌‌‌‍​‌​​‌​‌‌‍​‌‌​‌​​‌‍‌​‌​‌‌‌​‍​​‌​‌​​​‍‌‌‌​‌​‌‌‍​‌‌‌‌‌​​‍​​‌​​‌‌‌‍​‌‌‌‌​‌​‍‌​​​‌​​​‍​​​​‌​‌​‍​​​​‌‌‌‌⁠‍⁠

Overview

Rich Hickey is the creator of Clojure and Datomic. His legendary talks "Simple Made Easy" and "The Value of Values" challenge conventional programming wisdom and advocate for simplicity, immutability, and treating data as a first-class citizen.

Core Philosophy

"Simple is not easy. Easy is familiar. Simple is about lack of interleaving."

"State. You're doing it wrong."

"It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures."

Hickey distinguishes between "simple" (not intertwined) and "easy" (familiar, nearby). He argues we should pursue simplicity even when it's not easy.

Design Principles

Simple ≠ Easy: Simple means not complected (intertwined). Pursue it.

Values Over State: Immutable values simplify everything.

Data > Objects: Plain data with generic functions beats object hierarchies.

Explicit State: When state is needed, manage it explicitly.

When Writing Code

Always

  • Prefer immutable data structures

  • Use maps, vectors, sets—plain data

  • Separate data from functions

  • Make state changes explicit and controlled

  • Design with time in mind (values don't change)

  • Question complexity—is this complected?

Never

  • Conflate simple with easy

  • Hide state in objects

  • Create unnecessary abstractions

  • Reach for classes when data suffices

  • Ignore the cost of complexity

  • Complect things that could be separate

Prefer

  • Maps over objects

  • Pure functions over methods

  • Composition over inheritance

  • Declarative over imperative

  • Data literals over constructors

  • Namespaced keywords over types

Code Patterns

Data Orientation

;; BAD: Object-oriented thinking (defrecord Person [name age email]) (defn person-greet [person] (str "Hello, " (:name person)))

;; GOOD: Just use maps—they're data (def person {:name "Alice" :age 30 :email "alice@example.com"})

;; Generic functions work on all maps (defn greet [entity] (str "Hello, " (:name entity)))

;; Works for any map with :name (greet {:name "Bob" :type :user}) (greet {:name "Acme" :type :company})

;; 100 functions on 1 data structure ;; All of these work on your map: (get person :name) (assoc person :age 31) (update person :age inc) (select-keys person [:name :email]) (keys person) (vals person) (merge person {:title "Dr."})

Immutability

;; Data doesn't change—you create new data (def v1 [1 2 3]) (def v2 (conj v1 4))

v1 ;; Still [1 2 3] v2 ;; [1 2 3 4]

;; Structural sharing makes this efficient ;; v1 and v2 share structure in memory

;; Update nested structures with assoc-in, update-in (def user {:name "Alice" :address {:city "Portland" :zip "97201"}})

(def updated (assoc-in user [:address :city] "Seattle")) ;; user is unchanged, updated has new city

;; No defensive copying needed (defn process [data] ;; data cannot be mutated, safe to pass around (transform data))

Explicit State with Atoms

;; When you need state, make it explicit (def counter (atom 0))

;; Read state @counter ;; 0

;; Update state (pure function applied atomically) (swap! counter inc) ;; 1 (swap! counter + 10) ;; 11

;; State is in ONE place, not scattered through objects ;; Updates are explicit, not hidden in setters

;; For complex state, use a single atom with a map (def app-state (atom {:users {} :sessions {} :config {:debug false}}))

;; Update specific parts (swap! app-state assoc-in [:config :debug] true) (swap! app-state update-in [:users] assoc "alice" {:name "Alice"})

Simple vs Easy

;; EASY but COMPLEX: Object with intertwined concerns ;; - State + identity + behavior all mixed ;; - Hard to test, hard to reason about (defprotocol OrderProcessor (add-item [this item]) (remove-item [this item-id]) (calculate-total [this]) (submit [this]))

;; SIMPLE: Separate concerns ;; Data (just values) (def order {:items [] :status :draft})

;; Pure functions (no state) (defn add-item [order item] (update order :items conj item))

(defn calculate-total [order] (reduce + (map :price (:items order))))

;; Side effects isolated (defn submit-order! [order] (db/save! order) (email/send-confirmation! order))

;; Each piece is: ;; - Testable in isolation ;; - Understandable alone ;; - Recombinable

Spec for Data Validation

(require '[clojure.spec.alpha :as s])

;; Describe your data (s/def ::name string?) (s/def ::age pos-int?) (s/def ::email (s/and string? #(re-matches #".+@.+" %)))

(s/def ::person (s/keys :req-un [::name ::age ::email]))

;; Validate (s/valid? ::person {:name "Alice" :age 30 :email "alice@example.com"}) ;; true

;; Explain failures (s/explain ::person {:name "Alice" :age -5 :email "bad"}) ;; :age - failed: pos-int? ;; :email - failed: regex match

;; Generate test data (require '[clojure.spec.gen.alpha :as gen]) (gen/sample (s/gen ::person))

Transducers for Composition

;; Problem: each step creates intermediate collections (->> data (map transform) ;; new collection (filter valid?) ;; new collection (take 10)) ;; new collection

;; Solution: transducers compose without intermediate collections (def xform (comp (map transform) (filter valid?) (take 10)))

;; Apply to any collection type (into [] xform data) ;; vector (into #{} xform data) ;; set (transduce xform + 0 data) ;; reduce

;; Same transformation, different contexts ;; No intermediate collections created

Managing Time

;; Values are immutable—they represent a point in time ;; This enables powerful patterns:

;; 1. History (undo/redo) (def history (atom [])) (def current (atom {:count 0}))

(defn update-with-history! [f & args] (swap! history conj @current) (swap! current #(apply f % args)))

(defn undo! [] (when (seq @history) (reset! current (peek @history)) (swap! history pop)))

;; 2. Snapshotting (defn snapshot [] @app-state) ;; Returns immutable value

(def before (snapshot)) ;; ... make changes ... (def after (snapshot))

;; Compare states directly (= before after) (clojure.data/diff before after)

The Complectedness Test

Ask these questions:

  • Is state complected with identity? Separate them with values + refs.

  • Is behavior complected with data? Use data + functions, not objects.

  • Is order complected with logic? Use declarative over imperative.

  • Is specificity complected with generality? Use generic data structures.

Mental Model

Hickey approaches design by asking:

  • Is this simple or just easy? Familiar ≠ simple

  • What is this complected with? Find the intertwining

  • Is this data or process? Separate them

  • Where does state live? Make it explicit

  • What happens over time? Values + references

Signature Hickey Moves

  • Maps for everything

  • Pure functions on immutable data

  • Atoms for explicit state

  • Transducers for composable transforms

  • Spec for data validation

  • Separating complected concerns

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

renaissance-statistical-arbitrage

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

google-material-design

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

aqr-factor-investing

No summary provided by upstream source.

Repository SourceNeeds Review