malli

respatialized 2025-07-02T12:04:27.129369Z

Is there a version of :or that allows for specifying a :dispatch function the way :multi does? My specific use case is matching against a value's metadata - something like:

(let [s (m/schema [:or {:dispatch meta} [:map [:k1 :keyword]] [:map [:k2 :keyword]]])]
  (m/validate s ^{:k1 :test} [1])) ;; => false
While it's possible to use a :fn schema for this, matching a transformed value against a schema (as opposed to :multi, which seems like it's for concrete values) could be a cleaner and more declarative method of solving this (and possibly other) problems.

Ben Sless 2025-07-02T13:52:45.829219Z

You can use multi with dispatch function comp boolean meta

respatialized 2025-07-02T14:15:51.016439Z

could you show me an example?

Ben Sless 2025-07-02T15:11:40.770759Z

Remind me in a couple of hours if I forget, I should get to it by then

Ben Sless 2025-07-02T15:20:43.530749Z

Here you go

(m/validate
 [:multi {:dispatch (comp boolean meta)}
  [true vector?]
  [false map?]]
 (with-meta [1 2] {:some :thing}))


(m/validate
 [:multi {:dispatch (comp boolean meta)}
  [true vector?]
  [false map?]]
 (with-meta {1 2} {:some :thing}))


(m/validate
 [:multi {:dispatch (comp boolean meta)}
  [true vector?]
  [false map?]]
 {1 2})

🤔 1
respatialized 2025-07-02T19:38:44.637819Z

Thanks for this example - it seems like it can test for the presence or absence of metadata on values, and then match those values against a schema. However, I'm trying to validate against specific key/value pairs within a value's metadata map.

respatialized 2025-07-02T20:12:36.624289Z

I think this may be more general than :or or :multi; the basic idea is "match value x against schema s after calling function f on it." (s may or may not be itself a :or / :multi schema). In function form:

(defn comply
  "Call function f on the value and then match it against the given schema"
  [f schema value]
  (m/validate schema (f value)))

(comply meta
        [:map [:k1 :string] [:k2 {:optional true} :int]]
        ^{:k1 "test"} [1 2 3])
(comply meta [:map [:k1 :string] [:k2 {:optional true} :int]] ^{:k2 3} [1 2 3])
(comply meta [:map [:k1 :string] [:k2 {:optional true} :int]] [1 2 3])

respatialized 2025-07-02T20:15:02.497659Z

While this may work for some cases, I think problems with the function approach would arise in cases where there's a local registry.

Ben Sless 2025-07-03T05:27:43.542789Z

In that case I'd write a new schema type which refers to the object's meta.

Ben Sless 2025-07-03T05:28:26.910629Z

Use: [:meta ?schema]

respatialized 2025-07-13T16:08:11.276899Z

In implementing a custom schema, I think I'm struggling a bit with reify due to lack of experience. Here's a simplified example:

(require '[malli.core :as m :refer [AST -to-ast -from-ast -from-child-ast]])

(defn -meta-schema
  []
  ^{:type ::m/into-schema}
  (reify
   AST
     (-from-ast [parent ast options (-from-child-ast parent ast options)])))
I get the following error:
Syntax error (IllegalArgumentException) compiling reify* at (notebooks/net/respatialized/kindly_spec.clj:578:3).
Can't define method not in interfaces: _from_ast

Ben Sless 2025-07-13T16:18:11.813259Z

you accidentally slurped the body into the params, it seems

Ben Sless 2025-07-13T16:18:36.428749Z

Simplified:

(defn -meta-schema
  []
  ^{:type ::m/into-schema}
  (reify
    AST
    (-from-ast [parent ast options]
      (-from-child-ast parent ast options))))

🫠 1
respatialized 2025-07-13T17:31:57.095159Z

PEBKAC, whoops, thanks!

respatialized 2025-07-04T13:45:55.825489Z

https://github.clerk.garden/respatialized/kindly-spec/commit/86a33606f3e41a590fb0e59ae056b4607e8844c4/#a-custom-malli-schema-for-metadata I have a very basic example of a custom schema for a value's metadata using m/-simple-schema, detailed a bit in this notebook that outlines a specification for https://github.com/scicloj/kindly values, which use metadata for annotation purposes. However, I did encounter an issue when trying to use :refs and local registries which I noted there. I'm wondering if -simple-schema isn't the right constructor for the schema I'm trying to build, but this is also my first time extending malli in this way so it may just be my lack of experience with custom schemas. https://cloogle.phronemophobic.com/name-search.html?q=malli.core%2F-simple-schema&tables=var-usages was super helpful for finding examples of custom schemas, but all outside of malli appear to be made with m/-simple-schema. The only examples I could find of the m/-into-schema protocol were from malli.core itself - unsurprisingly they were quite complex! A more detailed doc or blog post on the topic of writing custom schemas would be super helpful for future guidance on the topic.

Ben Sless 2025-07-04T13:58:37.125399Z

It's been a while since I messed around with registries, hope someone else can think of a straightforward solution

2025-07-08T15:07:49.361239Z

the problem is that -simple-schema's child is not a schema. its use of walk-leaf gives it away. instead of walking its children, it considers itself a leaf. (walking is how registries are propagated)

2025-07-08T15:08:22.111189Z

you need a constructor where the schema is a child. -collection-schema is an example.

2025-07-08T15:09:39.318139Z

I would adapt -maybe-schema instead.

🤔 1
2025-07-08T15:10:51.614619Z

Copy that constructor and change the -validator and -explainer to remove the nil? checks. Then make sure (validator (meta x)) is called instead of (validator x). Ditto in ->parser.

🤔 1
2025-07-08T15:13:19.982389Z

Change the occurrences of :maybe to ::meta.

👀 1
2025-07-08T15:14:34.050169Z

And make sure to update the ::into-schema keywords to ::m/into-schema. Ditto ::schema.

2025-07-08T22:34:48.358049Z

you might also be able to use -proxy-schema but the tradeoffs aren't immediately clear to me. it might be fine.

respatialized 2025-07-08T22:38:11.058759Z

thank you! this is really helpful guidance. I will try this out and update the notebook based on the revisions I make.