m/walk doesn't preserve symbolic references if metadata is present
When walking types, m/walk is pretty destructive, I've found:
user> (m/walk [:schema {:registry {:a/x 'int?}} :a/x] (m/schema-walker identity))
=> [:schema {:registry #:a{:x int?}} :a/x]
user> (m/walk [:schema {:registry {:a/x 'int?}} [:a/x {}]] (m/schema-walker identity))
=> [:schema {:registry #:a{:x int?}} int?]
user> (m/walk [:schema {:registry {:a/x 'int?}} [:a/x {}]] (fn [a b c d] a))
=> [:schema {:registry #:a{:x int?}} int?]
Note that in the latter cases, the symbol reference to the type :a/x has been dereferenced in the output.
This is with ::m/walk-refs, ::m/walk-schema-refs and ::m/walk-entry-vals all unset (I believe -- my options map is nil).
Do we think it might be practical to have the walker preserve structure, such that
(m/walk ?schema (m/schema-walker identity))
would return a value that's (mu/equals ...) the original?
To provide an example of something that's kinda hard to do because of this state of affairs:
I'd like to write a function which walks a schema like this:
(1) [:map [:k/d [:map [:a/x {} :a/x]]]]
and rewrites uses of the type :a/x to another type (say :b/x) without changing anything else, so:
(2) [:map [:k/d [:map [:a/x {} :b/x]]]]
This works fine on the type:
(3) [:map [:k/d [:map [:a/x :a/x]]]]
but trying to do this on (1) with m/walk simply ends up deref-ing :a/x inline and giving me a result like:
(4) [:map [:k/d [:map [:a/x {} 'int?]]]]
... and as you can see above, even applying identity causes the dereference.
Also even applying println to all the arguments of Walker shows that there's no information I could use to reconstruct :a/x either (this is a simplified example):
(m/-walk
(m/schema [:a/x {}] {:registry (mr/composite-registry {:a/x 'int?} (m/default-schemas))})
(reify m/Walker
(-accept [this schema path options]
(println "accept" this schema path options) schema)
(-inner [this schema path options]
(println "inner" this schema path options) (m/-walk schema this path options))
(-outer [this schema path children options]
(println "outer" schema path children options) schema))
[]
nil)
... yields ...
accept #object[common.malli$eval81436$reify__81437 0x40b235f common.malli$eval81436$reify__81437@40b235f] int? [] nil
outer int? [] [] nil
I'm realizing that this may have more to do with how schemas are initially constructed?
user> (def options {:registry (mr/composite-registry (m/default-schemas) {:a/x 'int?})})
user> (m/schema :a/x options)
:a/x
user> (m/schema [:a/x {}] options)
int? ; (this is the m/form of this schema too)
;; because:
user> (@#'m/-schema :a/x options)
int?
;; due to the `vector?` branch of `m/schema`, which performs `m/-schema`, which performs `m/-lookup`
Even more so than above, this seems like a case where the form should reflect the way in which this was constructed?
In this case:
[:schema {:metadata :goes-here} :a/x]
the :a/x is preserved, whereas:
[:a/x {:metadata :goes-here}]
it's not.