This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
What if malli had a custom edn-file (`malli.edn`) that was used for all project-spesific config, e.g. read at malli.core
startup? Would allow better reuse (between projects) of good configs (registry, custom errors, sci-settings etc). Starting from something like this:
{:dev-mode true
:registry (malli.registry/composite-registry
(malli.core/default-schemas)
(malli.experimental.time/schemas)
(malli.util/schemas))}
malli.edn
{:dev-mode true
:registry (malli.registry/composite-registry
(malli.core/default-schemas)
(malli.experimental.time/schemas)
(malli.util/schemas))}
user.clj
(ns user
(:require [clojure.walk :as walk]
[clojure.edn :as edn]
[malli.core :as m]
[malli.registry :as mr]))
(defn -walk-requiring-resolve [x]
(walk/prewalk #(cond-> % (qualified-symbol? %) (requiring-resolve)) x))
(defn -read-config []
(-> (slurp "malli.edn")
(edn/read-string)
(-walk-requiring-resolve)
(update :registry eval))) ;; (/_\)
(mr/set-default-registry!
(:registry (-read-config)))
(m/deref-recursive
[:merge
[:map [:x :int]]
[:map [:y :int]]])
; => [:map [:x :int] [:y :int]]
My first thought is that thiswould be good, as all the configs & boilerplate of wiring things together would be in one place -> no need to pass options
map with everywhere.
It sounds like this is thinking from a solution rather than from a clear problem statement. The problem statement could be: Passing a config options map everywhere is a bit annoying, what alternative solutions are there to this?
(I'm referring to the spreadsheet stuff that Rich Hickey alluded to in his latest talk)
Personally I don't like having extra files at project root, it feels like logic is scattered around this way. I like the global atom kind of approach.
Speaking from personal experience, I'm writing a full stack, mostly frontend app. Shadow has its own config and at the beginning it was a good enough approach but then I noticed I started to have some duplication in build declaration logic, so I made my own build script, defined cljs dependencies in deps.edn
and I didn't need a separate file for shadow config. This was really nice because I know that I just have to look at deps.edn
and my build script and I don't have to worry about jumping through several files.
I think the best approach would be to define some function, say set-config!
. Developers only need to handle how this function is called and they have the full freedom to shape their build process as needed.
thanks. Currently, there is already an atom for the global registry (`malli.registry/registry*`), would be better if the whole options
could be swapped. My original issue for this is from 2020: https://github.com/metosin/malli/issues/228. To get best out of this, some options would have to be changed from non-qualified keys to qualified to avoid collisions.
currently, if one want’s to disable sci by default, one has to put ::m/disable-sci
via options for all the public functions. Same would be if the https://github.com/metosin/malli/pull/1043 would be merged, e.g. option to disable it.
I personally like a file as a easy way to configure this (on top of the options
atom replacing the registry
atom), as it would also remove the need for extra type
and mode
arguments:
;; - cljs: :closure-defines {malli.registry/type "custom"}
;; - clj: :jvm-opts ["-Dmalli.registry/type=custom"]
e.g. currently, when malli.core
loads:
1. by default, it adds all schemas into registry (no DCE for cljs)
2. … unless type
or mode
are set, in case you can start with empty registry and add just the things you need via mr/set-default-registry!
(DCE can be used to remove 90%+ of the code for cljs)
with the optional malli.edn
file, malli.core
would:
1. load the file (if exists), instantiate the registry defined by it
2. if no file present, just use the default registry (all schemas)
3. no type
or mode
needed (due to 1 - just include the schemas you need)
one idea is a defn-generating macro, that generates malli's api with default options. No state involved, no sharing issues if you pull another library that also uses malli with default options.
(ns my-apps.malli.core (:require [malli.core :as m])
(def my-default-options {::m/disable-sci true})
(m/create-malli-core-api my-default-options)
; => (defn validate ([v] (validate v my-default-options)) ([v options] (m/validate v options))) (defn explain ...) ... etc...
@U055NJ5CC As long as there is a nice programmatic API, I think a separate file is a fine approach because that would make the initial global registry setting as smooth as possible.
@U055XFK8V isn't a macro an overkill? Couldn't malli just read from a dynamic var which you could toggle in namespace level (just like ns
macro sets *ns*
or how all printing is redirected when you rebind *out*
)
@U02S4QZAH61 certainly there's costs, in particular each app has their own api. IMO it's the most robust solution if that's a worthy cost to pay to be impervious to parallelism issues or clashes with other libraries.
just because code occurs in a namespace doesn't mean it will be evaluated in it either
oh, so that means that dynamic vars are not parallel-computing friendly?
once you kick off another thread, you need to convey the bindings to the new thread to keep the same context. many but not all clojure ops do this.
but my point is more:
(ns ns1)
(defn a [] *ns*)
(ns ns2
(defn b [] (a))
(ns ns3)
(ns2/b) => prints ns3
Oh yeah, I recently realized that *ns*
doesn't refer to the file namespace. so my analogy is wrong.
the best way to determine which namespace you occur in is with syntax-quote or a namespaced keyword.
(defn this-ns [] (-> ::a namespace symbol))
that looks hacky but I appreciate the explanation