malli

eskos 2026-05-07T08:46:28.672749Z

Forking from discussion above as this might be more philosophical and it's not really needed/directly related - is this expected when generating values, and on the other hand, how would one negate such map value anyway. Assumption here is that the :map comes from elsewhere so easy in-place negation/relaxing is not possible.

(mg/sample [:map [:summer [:= true]]] {:size 10})
=>
({:summer true}
 {:summer true}
 {:summer true}
 {:summer true}
 {:summer true}
 {:summer true}
 {:summer true}
 {:summer true}
 {:summer true}
 {:summer true})
(mg/sample [:not [:map [:summer [:= true]]]] {:size 10})
=> (#{} nil #{} #{} 1 nil () [-959053578399N "!"] {} 0.5)
I'm thinking that behaviorally := 's generator should probably be able to at least try to create a complement of whatever constant it is given, but this feels like a massive change...

2026-05-08T22:14:48.069829Z

I've had good success writing a general negation algorithm for schemas. (generate [:not S]) => (generate (negate S)) https://github.com/frenchy64/malli/pull/4/changes

2026-05-08T22:17:08.147089Z

e.g.,

[:not [:map [:a [:= 1]]]]
=>
[:or
 [:not #'clojure.core/map?]
 ;; missing :a
 [:map [:a {:optional true} :never]]
 ;; bad :a
 [:map [:a [:not= 1]]]]

eskos 2026-05-09T04:40:32.542419Z

I was thinking last evening that I'd actually be open to a transformer which uses a multimethod for handling the leaves of any given model structure; given path in tree and the value itself one could do generic/semi-generic/specific transformations on each value, and as extension of such transformer building a complementer would nicely slice the problem into two between walking and actual transforming. I haven't looked but maybe the json key transformer already does something like this in principle 🤔

2026-05-09T05:52:29.258029Z

I don't understand the problem scenario. What does this mean: "Assumption here is that the :map comes from elsewhere so easy in-place negation/relaxing is not possible."

2026-05-12T19:52:12.595319Z

Ok, I missed that you were talking about building the complement by hand. The idea I pointed to above is about writing an algorithm to build the complement schema automatically.

eskos 2026-05-07T09:06:42.963829Z

Basically I'm actually thinking that when generating values, it works with some sort of category/tree with clauses such as "should be truthy/falsey value", "should be none/one value/many values". This is logic, but not in the computer science sense but more in the humanistic sciences.

opqdonut 2026-05-07T10:24:22.606809Z

yeah you probably want to limit it to the original shape, using something like

[:and [:map :summer] [:not [:map [:summer [:= true]]]]]

opqdonut 2026-05-07T10:24:52.775679Z

alternatively, have some mechanism that pushes negations down to the leaves with something like

[:map [:summer [:not [:= true]]]]

opqdonut 2026-05-07T10:26:12.530449Z

for something like fuzzing an API you want something like a generator, that creates a value that matches a complex schema and then intentionally mutates it just a bit so it breaks the schema

opqdonut 2026-05-07T10:26:37.313199Z

:not 's generator could do this, but it's probably not the right place

eskos 2026-05-07T10:31:55.342739Z

There probably is some kind of walker/mutator option which could do this recursive fiddling for an existing schema, as the assumption here is that I can't directly just modify that [:map [:summer [:= true]]] definition but I could always do (walk map-schema complement-all-the-things)

opqdonut 2026-05-07T10:36:55.341749Z

yep!

eskos 2026-05-10T03:45:04.224969Z

elsewhere -> it's a schema already defined elsewhere in code, being used in its exact form by rest of the system

eskos 2026-05-10T03:46:53.815939Z

It's a maintenance hell to build multiple schemas for different features. I'm currently working with a type which has ~50 top level optional map keys alone, so it would be insane to build a secondary complement schema by hand on the side and keep it logically matching, especially as the schema does evolve over time.

eskos 2026-05-10T03:57:06.276639Z

(fwiw this particular type does get the credit for being the source of insanity, but alas, legacy is what people before us do without considering future maintenance properly...)