Last active
June 9, 2025 17:50
-
-
Save isaksky/d5f72810199cdc6460fac6d6f633d368 to your computer and use it in GitHub Desktop.
omit-nils reader macro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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