Skip to content

Instantly share code, notes, and snippets.

@isaksky
Last active June 9, 2025 17:50
Show Gist options
  • Save isaksky/d5f72810199cdc6460fac6d6f633d368 to your computer and use it in GitHub Desktop.
Save isaksky/d5f72810199cdc6460fac6d6f633d368 to your computer and use it in GitHub Desktop.
omit-nils reader macro
(ns com.isaksky.belt.omit-nils)
;; License: MIT
;; Note: 4 defs here are stolen from tonsky/clojure-plus:
;; https://github.com/tonsky/clojure-plus/blob/fdb3675a2760ac5ec60da5c58ed0cc8e9b6d6f80/src/clojure%2B/hashp.clj
;; - config, defualt-config
;; - install!, uninstall!
(defn- default-config []
{:symbol 'omit-nils})
(def config
(default-config))
(defn form-might-be-nil? [x]
(cond
(nil? x) true
(number? x) false
(string? x) false
(keyword? x) false
(boolean? x) false
(symbol? x) true
(list? x) true
(map? x) false
(vector? x) false
:else true))
(defn vec-omit-nils* [forms]
(let [[initial-forms remaining-forms]
(split-with (fn [x] (not (form-might-be-nil? x))) forms)]
(cond
(empty? remaining-forms)
(vec initial-forms)
:else
(let [ret-sym (gensym "vector")]
(list
'let
(into [ret-sym (list 'transient (vec initial-forms))]
(->> remaining-forms
(mapcat
(fn [x]
(cond
(nil? x) []
(form-might-be-nil? x)
[ret-sym
(list 'if (list 'nil? x)
ret-sym
(list 'conj! ret-sym x))]
:else [ret-sym (list 'conj! ret-sym x)])))))
(list 'persistent! ret-sym))))))
(defmacro vec-omit-nils [& xs]
(vec-omit-nils* (vec xs)))
(defn map-omit-nils*
[m]
(let [[initial-forms remaining-forms]
(split-with (fn [[k v]] (not (form-might-be-nil? v))) m)]
(cond
(empty? remaining-forms)
(into {} initial-forms)
:else
(let [ret-sym (gensym "map")]
(list
'let
(into [ret-sym (list 'transient (into {} initial-forms))]
(->> remaining-forms
(mapcat
(fn [[k v]]
(cond
(nil? v) []
(form-might-be-nil? v)
(let [v' (gensym "v")]
[v' v
ret-sym
(list 'if (list 'nil? v')
ret-sym
(list 'assoc! ret-sym k v'))])
:else
[ret-sym (list 'assoc! ret-sym k v)])))))
(list 'persistent! ret-sym))))))
(defmacro map-omit-nils [& forms]
(map-omit-nils* (into [] forms)))
(defn omit-nils
[form]
(cond
(vector? form)
(vec-omit-nils* form)
(map? form)
(map-omit-nils* form)
:else
(throw (ex-info "Form not supported. Expected vector or map literal." {:form form}))))
(defn install!
([]
(install! {}))
([opts]
(let [config (merge (default-config) opts)
_ (.doReset #'config config)
sym (:symbol config)]
(alter-var-root #'*data-readers* assoc sym #'omit-nils)
(when (thread-bound? #'*data-readers*)
(set! *data-readers* (assoc *data-readers* sym #'omit-nils))))))
(defn uninstall! []
(let [sym (:symbol config)]
(alter-var-root #'*data-readers* dissoc sym)
(when (thread-bound? #'*data-readers*)
(set! *data-readers* (dissoc *data-readers* sym)))))
(comment
(install!)
(def nil-def nil)
(defn fn-returning-nil [] nil)
(let [d nil
e :e]
#omit-nils {:a :a
:b nil-def
:c (fn-returning-nil)
:d d
:e e})
(clojure.walk/macroexpand-all
'(let [d nil
e :e]
#omit-nils {:a :a
:b nil-def
:c (fn-returning-nil)
:d d
:e e}))
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment