Enums (defenum)
Calcit enums are tagged unions — each variant has a tag (keyword) and zero or more typed payload fields. Under the hood enums are represented as tuples with a class reference.
Quick Recipes
- Define:
defenum Shape (:circle :number) (:rect :number :number) - Create:
%:: Shape :circle 5 - Match:
tag-match shape ((:circle r) ...) ((:rect w h) ...) - Type Check:
assert-type shape :enum
Defining Enums
let
Color $ defenum Color (:red) (:green) (:blue)
c $ %:: Color :red
println c
; => (%:: :red (:enum Color))
Variants with payloads:
let
Shape $ defenum Shape (:circle :number) (:rect :number :number)
c $ %:: Shape :circle 5
r $ %:: Shape :rect 3 4
println c
; => (%:: :circle 5 (:enum Shape))
println r
; => (%:: :rect 3 4 (:enum Shape))
Creating Instances
Use %:: with the enum definition, the variant tag, and then the payload values:
let
ApiResult $ defenum ApiResult (:ok :string) (:err :string)
ok $ %:: ApiResult :ok |success
err $ %:: ApiResult :err |network-error
println ok
println err
Pattern Matching with tag-match
tag-match branches on the variant tag and binds payload values to names:
let
Shape $ defenum Shape (:circle :number) (:rect :number :number)
c $ %:: Shape :circle 5
area $ tag-match c
(:circle radius)
* radius radius 3.14159
(:rect w h)
* w h
println area
; => 78.53975
Multi-line branch bodies (required when the body is more than a single call):
let
ApiResult $ defenum ApiResult (:ok :string) (:err :string)
ok $ %:: ApiResult :ok |success
describe $ fn (r)
tag-match r
(:ok msg)
str-spaced |OK: msg
(:err msg)
str-spaced |Error: msg
println (describe ok)
; => OK: success
Zero-payload Variants
When a variant has no payload, the pattern is just the tag:
let
MaybeInt $ defenum MaybeInt (:some :number) (:none)
some-val $ %:: MaybeInt :some 42
none-val $ %:: MaybeInt :none
extracted $ tag-match some-val
(:some v)
* v 2
(:none) nil
println extracted
; => 84
Checking Enum Origin
Use &tuple:enum to verify a tuple belongs to a specific enum:
let
ApiResult $ defenum ApiResult (:ok :number) (:err :string)
x $ %:: ApiResult :ok 1
println $ = (&tuple:enum x) ApiResult
; => true
Common Patterns
Result / Either type
let
AppResult $ defenum AppResult (:ok :number) (:err :string)
compute $ fn (x)
if (> x 0)
%:: AppResult :ok (* x 10)
%:: AppResult :err |negative-input
handle $ fn (r)
tag-match r
(:ok v)
str-spaced |result: v
(:err e)
str-spaced |failed: e
println $ handle (compute 5)
; => result: 50
println $ handle (compute -1)
; => failed: negative-input
Compose enums with functions
let
Status $ defenum Status (:pending) (:done :string) (:failed :string)
pending $ %:: Status :pending
done $ %:: Status :done |ok
is-done $ fn (s)
tag-match s
(:done _) true
(:pending) false
(:failed _) false
println (is-done pending)
; => false
println (is-done done)
; => true
Type Annotations
Field types in defenum declarations participate in type checking:
; (:ok :string) means the :ok variant has one :string payload
defenum ApiResult (:ok :string) (:err :string)
; (:point :number :number) means :point has two :number payloads
defenum Shape (:point :number :number) (:circle :number)
; (:none) means no payload
defenum MaybeInt (:some :number) (:none)
Runtime type validation is enforced at instance creation — passing the wrong type to %:: will raise an error.
Notes
- Enum instances are immutable tuples with a class reference.
tag-matchis exhaustive match; unmatched tags raise a runtime error.- Use
&tuple:nthto directly access payload values by index (0 = tag, 1+ = payloads). - Enums vs plain tuples: plain
:: :tag valtuples have no class;%:: Enum :tag valtuples carry their enum class for origin checking.
See Also
- Tuples — raw tagged tuples without a class
- Records — named-field structs with
defstruct - Static Analysis — type checking for enum payloads