Vary is a Clojure library for defining sum-type data structures with a concise hashmap syntax, Malli validation, and pattern matching.
varymacro for succinct variant definitions- Simple keywords for cases
casesfunction for associated values inspection.matchmacro for pattern matching.- Auto-generated accessors
- REPL-friendly for interactive workflows.
For Clojure CLI/deps.edn:
com.biutthapa/vary {:mvn/version "0.0.1"}For Leiningen/Boot:
[com.biutthapa/vary "0.0.1"]Define variants:
(ns coffee-shop
(:require [vary.core :refer [vary cases match]]))
;; Simple variant with keywords
(vary Color #{:red :blue :green})
;; Variant with associated values
(vary CoffeeSize
{:small {:label "Small" :price 3.50 :volume 8}
:medium {:label "Medium" :price 4.00 :volume 12}
:large {:label "Large" :price 4.50 :volume 16}})
;; Variant with fields
(vary OrderStatus
{:pending []
:completed [:amount :double]
:failed [:reason :string]})Inspect cases:
(cases Color)
;; => {:red {}, :blue {}, :green {}}
(cases CoffeeSize)
;; => {:small {:label "Small" :price 3.50 :volume 8}, ...}Iterate through associated values (for variants with associated values):
(doseq [[size props] (cases CoffeeSize)]
(println (:label props) ": $" (format "%.2f" (:price props)) " for " (:volume props) " oz"))
;; => Small: $3.50 for 8 oz
;; Medium: $4.00 for 12 oz
;; Large: $4.50 for 16 ozUse match for pattern matching:
;; For simple cases
(defn color-name [color]
(match color Color
:red "Red"
:blue "Blue"
:green "Green"))
(println (color-name :blue)) ;; => "Blue"
;; For fields
(defn handle-order [order]
(match order OrderStatus
:pending "Order is pending"
[:completed amount] (str "Completed with amount: " amount)
[:failed reason] (str "Failed: " reason)))
(println (handle-order {:type :completed :amount 5.50})) ;; => "Completed with amount: 5.50"Access associated values with auto-generated accessors (only for variants with associated values):
(defn order-summary [order]
(let [size (:size order)]
(str "Order: " (:coffee-type order) " (" (CoffeeSize-label size)
", " (CoffeeSize-volume size) " oz) - $" (format "%.2f" (CoffeeSize-price size)))))
(println (order-summary {:coffee-type "Latte" :size :medium}))
;; => Order: Latte (Medium, 12 oz) - $4.00Note: No accessors are generated for simple variants like Color.
Add the following to your .clj-kondo/config.edn:
{:lint-as {vary.core/vary clojure.core/def}
:hooks
{:analyze-call
{vary.core/vary
{:analyze
(fn [{:keys [:node]}]
(let [children (:children node)
vary-name (when (>= (count children) 2) (nth children 1))
case-map (when (>= (count children) 3) (nth children 2))
is-symbol-node (fn [n] (and (map? n) (= :token (:tag n))))
is-map-node (fn [n] (and (map? n) (= :map (:tag n))))
is-keyword-node (fn [n] (and (map? n) (= :keyword (:tag n))))
associated-values (when (and vary-name case-map
(is-symbol-node vary-name)
(is-map-node case-map))
(->> (:children case-map)
(partition 2)
(map second)
(filter is-map-node)
(mapcat :children)
(partition 2)
(map first)
(filter is-keyword-node)
(map :value)
(map name)
distinct))]
{:defined-by :clj-kondo.hooks/defined
:defined-fns (if (and vary-name associated-values (is-symbol-node vary-name))
(mapv (fn [field]
(symbol (str (name (:value vary-name)) "-" field)))
associated-values)
[])}))}}}}MIT License. See LICENSE file.