F# and Clojure both make immutable data the comfortable path. The larger shift is from F# records and statically checked fields to a small set of generic persistent collections.
Table of contents
Collection Literals
| Intent | F# | Clojure |
|---|---|---|
| Persistent indexed data | No direct core equivalent | [:read :eval :print] |
| Linked list | [ "read"; "eval"; "print" ] | '(:read :eval :print) |
| Key-value data | Map [ "name", "Ada" ] | {:name "Ada" :active? true} |
| Unique values | set [ "clojure"; "lisp" ] | #{:clojure :lisp} |
Vectors are the usual default when order matters. Lists are especially important because Clojure code itself is represented with lists.
Reading Nested Data
Keywords can look themselves up in a map:
profile.Name
// "Ada"
profile.Skills |> List.item 1
// "Clojure"F#
(:name profile)
;; => "Ada"
(get-in profile [:skills 1])
;; => "Clojure"Clojure
Use get for one lookup and get-in for a path through nested maps and vectors. A missing key returns nil; there is no F# compiler error or automatic Option, so validate maps at system boundaries when their shape matters.
Updating Without Mutation
F# record-copy syntax and Clojure’s assoc both return new values. update and conj generalize the idea across Clojure’s collection types:
let activated = { profile with Active = true }
let experienced =
{ activated with Skills = activated.Skills @ [ "Java" ] }F#
(def activated (assoc profile :active? true))
(def experienced (update activated :skills conj "Java"))
(:active? profile)
;; => false
(:active? experienced)
;; => true
(:skills experienced)
;; => ["Lisp" "Clojure" "Java"]Clojure
conj adds a value in the natural place for a collection: at the end of a vector, at the front of a list, and anywhere in a set.
Try It
Restock the apples while keeping the original inventory:
let inventory = Map [ "apples", 2; "pears", 3 ]
let restocked =
inventory
|> Map.change "apples" (Option.map (fun count -> count + 4))
inventory["apples"]
// 2
restocked["apples"]
// 6F#
(def inventory {:apples 2 :pears 3})
(def restocked (update inventory :apples + 4))
(:apples inventory)
;; => 2
(:apples restocked)
;; => 6Clojure
The old value remains available, so functions can safely share data without coordinating who is allowed to change it.