Language tour

spork-lang 0.6.0

A compact tour of Spork's syntax, functions, immutable collections, control flow, destructuring, macros, and async support.

Spork code is made of values and forms. Most forms are calls, while a small set of special forms provide definitions, bindings, control flow, classes, namespaces, and other language structure.

Values and calls#

Numbers, strings, booleans, and nil are Python values. Keywords evaluate to themselves and double as map lookup functions.

(def person {:name "Ada" :languages ["Analytical Engine" "Spork"]})

(:name person)              ; => "Ada"
(:missing person "unknown") ; => "unknown"
(first (:languages person)) ; => "Analytical Engine"

Parenthesized forms call the first value with the remaining values as arguments:

(+ 1 2 3)
(print "hello")
(max 10 42)

Spork identifiers conventionally use hyphens. They normalize to underscores when compiled, and a trailing question mark normalizes to _q.

Functions and local bindings#

Functions return the value of their final expression. let introduces sequential local bindings, so later values can use earlier ones.

(defn rectangle-area [width height]
  (let [validated-width (max width 0)
        validated-height (max height 0)]
    (* validated-width validated-height)))

(rectangle-area 6 7) ; => 42

Functions support positional, optional, keyword-only, variadic, and destructured parameters. The anonymous-function reader macro uses %, %2, and %& placeholders:

(def double #(* % 2))
(map double [1 2 3])

Persistent collections#

Vector, map, and set literals produce persistent data structures; sorted-vec constructs a persistent SortedVector. Operations such as assoc, conj, and dissoc return new values instead of mutating their inputs. Immutability is shallow: a stored Python object retains its own mutation behavior.

(def languages ["Spork" "Python"])
(def expanded (conj languages "Clojure"))

(print languages) ; ["Spork" "Python"]
(print expanded)  ; ["Spork" "Python" "Clojure"]

(def settings {:theme :dark :line-numbers true})
(def updated (assoc settings :theme :light))

Structural sharing makes this practical: unchanged internal structure is reused. Transient variants are available when an algorithm needs an efficient mutation phase before returning a persistent result.

Expression-oriented control flow#

if, cond, when, unless, let, do, match, and iteration forms all fit in expression positions.

(defn classify [value]
  (cond
    (< value 0) :negative
    (> value 0) :positive
    :else :zero))

(def labels
  (for [value [-2 0 4]]
    {:value value :kind (classify value)}))

for evaluates eagerly and returns a persistent vector. Use doseq for effect-only iteration; it avoids allocating a result and returns nil.

(for [x (range 5)] (* x x))
; => [0 1 4 9 16]

(doseq [name ["Ada" "Grace"]]
  (print (fmt "Hello, {}" name)))

sorted-for eagerly returns a persistent SortedVector and can accept a key or reverse ordering.

Destructuring and matching#

Bindings can pull values from sequential and associative structures. The same model is available in def, let, function parameters, and iteration.

(def user {:name "Grace" :role :admin})
(def {:keys [name role]} user)

(for [[key value] [[:language "Spork"] [:host "Python"]]]
  (fmt "{} = {}" key value))

Pattern matching handles literals, captures, sequence patterns, map patterns, class patterns, guards, and alternatives while compiling to Python's native match machinery.

(match message
  {:kind :text :body body} (print body)
  {:kind :quit}             :done
  _                         :unknown)

Namespaces and Python modules#

An ns form declares a namespace and its dependencies. :require loads Spork namespaces; :import uses Python's import system.

(ns report.core
  (:require [std.json :as json])
  (:import [datetime :refer [datetime timezone]]
           [pathlib :refer [Path]]))

There is no separate foreign-function syntax after import. Constructors, methods, exceptions, iterators, context managers, and async values remain their normal Python objects.

Macros#

Spork code is represented by the same list and persistent collection values available to a running program. Macros receive unevaluated forms and return forms for the compiler to continue compiling.

(defmacro unless-empty [value & body]
  `(if (seq ~value)
     (do ~@body)))

(unless-empty names
  (print "names were present"))

Quoting, quasiquoting, unquote, and unquote-splicing provide the usual tools for constructing expansions. Macros execute during compilation and should be treated as trusted project code.

Async, classes, and protocols#

Async functions and generators interoperate with Python event loops and async libraries:

(defn ^async fetch-all [client url-stream]
  (async-for [url url-stream]
    (await (client.get url))))

Spork also supports Python-compatible classes, decorators, type annotations, protocols, properties, exception handling, context managers, and generators. These features use Python objects and semantics rather than parallel runtime abstractions.

Full reference#

This tour introduces the working model rather than every form. Continue with the complete language reference for semantics and edge cases, and use the standard library reference for built-in values and functions.