Coming from F#, the functional instincts transfer: prefer values, compose small functions, and transform immutable data. The first surprises are mostly surface area: prefix notation, parentheses, dynamic typing, and a REPL that is central to development rather than merely a scratchpad.
Table of contents
On this page
From FSI to the REPL
FSI evaluates an F# expression interactively:
1 + 2 + 3;;
// val it: int = 6
Clojure’s read-eval-print loop does the same job with Clojure forms. After installing the .NET SDK and Clojure, launch either interactive environment from a terminal:
dotnet fsiF#
cljClojure
Reading Forms
Clojure code is made of forms. Where F# commonly places an operator between values, a Clojure list puts the function first:
let total = 1 + 2 + 3
let largest = max 4 (max 9 2)
let greeting = "Hello, " + "Clojure"F#
(+ 1 2 3)
;; => 6
(max 4 9 2)
;; => 9
(str "Hello, " "Clojure")
;; => "Hello, Clojure"Clojure
This is prefix notation. There is no separate infix grammar to learn, and nested forms are evaluated from the inside out:
2 * (3 + 4)
// 14F#
(* 2 (+ 3 4))
;; => 14Clojure
Common Values
F# usually infers a static type before code runs. Clojure values carry their types at runtime, and a Var can later point at a value of another type. The closest literals show both the overlap and the gaps:
type Status = Ready
let integer = 42
let fraction = 3.0 / 4.0
let greeting = "hello"
let status = Ready
let enabled = true
let missing: int option = NoneF#
42 ; integer
3/4 ; exact ratio
"hello" ; string
:ready ; keyword
true ; boolean
nil ; no valueClojure
Unlike the F# floating-point division above, 3/4 is an exact ratio. Keywords such as :ready are names that evaluate to themselves. They often fill the role that simple discriminated-union cases play in F#, although the compiler does not check that every possible keyword was handled.
Naming a Value
An F# let binds a name in its scope. Clojure’s top-level def creates a Var in the current namespace:
let language = "F#"F#
(def language "Clojure")
(def first-released 2007)
(str language " was first released in " first-released)
;; => "Clojure was first released in 2007"Clojure
Unlike an ordinary F# binding in a compiled module, a Var is an identity that can be redefined while the process keeps running. That is what makes evaluating one changed definition into a live REPL so useful. Use local let bindings inside functions once the experiment has a shape worth keeping.
Try It
Convert 20 degrees Celsius to Fahrenheit by evaluating this form:
let celsius = 20
let fahrenheit = 32 + (celsius * 9 / 5)
// 68F#
(def celsius 20)
(+ 32 (* celsius 9/5))
;; => 68Clojure
Change celsius, evaluate the def again, and rerun the conversion. That short feedback loop is the core of working in Clojure.