Functions remain the main unit of behaviour, and the last expression still provides the result. The important difference is that F# functions are curried by default, while a Clojure function declares explicit arities with parameter vectors.
Table of contents
On this page
Defining a Function
The direct translation of a small F# function with Clojure’s defn is pleasantly boring:
let greet name = $"Hello, {name}!"F#
(defn greet [name]
(str "Hello, " name "!"))
(greet "Ada")
;; => "Hello, Ada!"Clojure
Clojure names usually use kebab-case. Predicates conventionally end in ?, such as empty?, even?, or affordable?, instead of relying on a bool return type to communicate intent.
Currying Is Explicit
F# makes partial application automatic:
let totalWithTax taxRate price =
let tax = price * taxRate
price + tax
let addVat = totalWithTax 0.20mF#
(defn total-with-tax [tax-rate price]
(let [tax (* price tax-rate)]
(+ price tax)))
(def add-vat (partial total-with-tax 1/5))
(add-vat 100)
;; => 120Clojure
Clojure uses partial, so put the values likely to be fixed first.
Local Bindings
The total-with-tax function uses let for a name that belongs only to one calculation. Bindings are written as pairs inside a vector, and they exist only inside the let body.
Choosing a Result
F# uses if/elif for the same expression-oriented branch that Clojure writes with cond:
let temperatureLabel celsius =
if celsius < 0 then "freezing"
elif celsius < 20 then "cool"
else "warm"
temperatureLabel 22
// "warm"F#
(defn temperature-label [celsius]
(cond
(< celsius 0) :freezing
(< celsius 20) :cool
:else :warm))
(temperature-label 22)
;; => :warmClojure
These are expressions, so each branch produces a value rather than assigning one.
Destructuring Maps
F# normally destructures a known record type. Clojure destructuring pulls values from a generic collection while binding function parameters:
type Person = { Name: string; Role: string option }
let introduce { Name = name; Role = role } =
let resolvedRole = defaultArg role "guest"
$"{name} is a {resolvedRole}"
introduce { Name = "Grace"; Role = None }
// "Grace is a guest"F#
(defn introduce [{:keys [name role]
:or {role "guest"}}]
(str name " is a " role))
(introduce {:name "Ada" :role "maintainer"})
;; => "Ada is a maintainer"
(introduce {:name "Grace"})
;; => "Grace is a guest"Clojure
:keys binds map values to local names, while :or supplies defaults for missing keys.
Try It
Write a predicate that destructures a product and compares its price with a budget:
type Product = { Name: string; Price: int }
let affordable budget { Price = price } =
price <= budget
affordable 20 { Name = "Notebook"; Price = 12 }
// trueF#
(defn affordable? [{:keys [price]} budget]
(<= price budget))
(affordable? {:name "Notebook" :price 12} 20)
;; => trueClojure
Keeping the function pure makes it easy to call at the REPL with any sample data you need.