Functions

spork-lang 0.6.0

Anonymous, named, multi-arity, variadic, keyword, and destructured functions.

Spork functions are Python callables with Lisp parameter syntax, lexical closures, defaults, variadic parameters, destructuring, and optional multi-arity dispatch. This page defines invocation and function declaration forms.

Anonymous Functions#

(fn [x] (* x x))

(fn [x y]
  (let [sum (+ x y)]
    (* sum sum)))

Reader shorthand#

The #(...) reader macro provides a compact anonymous-function form with % placeholders. Use fn when names, destructuring, annotations, keyword-only parameters, or multiple body forms should be explicit.

Named Functions#

defn binds a function name. A string immediately after the parameter vector becomes the function's Python docstring.

(defn square [x]
  (* x x))

; With docstring
(defn greet [name]
  "Returns a greeting string."
  (fmt "Hello, {}!" name))

Multi-Arity Functions#

Instead of one parameter vector, a function may contain several parenthesized clauses. Each clause starts with its own parameter vector, and calls dispatch by argument count.

(defn greet
  ([name]
   (greet name "Hello"))
  ([name greeting]
   (fmt "{}, {}!" greeting name)))

(greet "Alice")           ; => "Hello, Alice!"
(greet "Alice" "Hi")      ; => "Hi, Alice!"

Variadic Functions#

Within a parameter vector, & name collects the remaining positional arguments under name.

; Rest arguments
(defn sum [& nums]
  (reduce + 0 nums))

(sum 1 2 3 4)  ; => 10

; Mixed positional and rest
(defn log [level & msgs]
  (print level ":" (.join "" (map str msgs))))

Keyword Arguments#

In a parameter vector, * separates positional parameters from keyword-only parameters. A bare name after * is required; (name default) supplies a default. ** name instead collects otherwise-unbound keyword arguments into a persistent map.

At a call site, *{:key value} converts entries to Python keyword arguments. The inline spelling * :key value is equivalent. A map variable can be splatted as *{options}; map variables and literal entries can also be combined inside the braces. More than one splat may follow the positional arguments.

; `age` and `email` are required keyword-only parameters
(defn create-user [name * age email]
  {:name name :age age :email email})

(create-user "Alice" *{:age 30 :email "alice@example.com"})
; => {:name "Alice" :age 30 :email "alice@example.com"}

; A two-item list declares a keyword-only parameter and its default
(defn config [host * (port 8080) (debug false)]
  {:host host :port port :debug debug})

(config "localhost")
; => {:host "localhost" :port 8080 :debug false}
(config "example.com" *{:port 3000})
; => {:host "example.com" :port 3000 :debug false}

; Inline keyword arguments follow a bare `*`
(config "example.com" * :port 3000 :debug true)
; => {:host "example.com" :port 3000 :debug true}

; `*{options}` splats every entry in a map variable
(def options {:port 4000 :debug true})
(config "example.com" *{options})
; => {:host "example.com" :port 4000 :debug true}

; Literal entries and map variables may share one splat
(def debug-options {:debug true})
(config "example.com" *{:port 5000 debug-options})
; => {:host "example.com" :port 5000 :debug true}

; `** opts` captures keyword arguments not bound to named parameters
(defn flexible [required ** opts]
  {:required required :opts opts})

(flexible "value" *{:a 1 :b 2})
; => {:required "value" :opts {:a 1 :b 2}}

; The same call syntax works with Python functions and methods
(def template "{name} is {age}")
(template.format *{:name "Alice" :age 30}) ; => "Alice is 30"

Destructuring in Parameters#

A vector parameter pattern binds values by position and may nest. & binds the remaining positions as a persistent vector. Too few positional values raise IndexError.

A map pattern using {:keys [name age]} creates local bindings from the map's :name and :age entries. The explicit form {local :source-key} can rename a keyword lookup, and a string may replace the source keyword. A missing map entry binds nil.

(defn process-point [[x y]]
  (+ x y))

(process-point [3 4]) ; => 7

(defn split-head [[head & tail]]
  [head tail])

(split-head [1 2 3]) ; => [1 [2 3]]

(defn greet-person [{:keys [name age]}]
  (fmt "{} is {} years old" name age))

(greet-person {:name "Mina" :age 29}) ; => "Mina is 29 years old"

(defn display-name [{label :name}]
  label)

(display-name {:name "Spork"}) ; => "Spork"