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...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
e.g.,
[:not [:map [:a [:= 1]]]]
=>
[:or
[:not #'clojure.core/map?]
;; missing :a
[:map [:a {:optional true} :never]]
;; bad :a
[:map [:a [:not= 1]]]]
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 🤔
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."
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.
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.
yeah you probably want to limit it to the original shape, using something like
[:and [:map :summer] [:not [:map [:summer [:= true]]]]]alternatively, have some mechanism that pushes negations down to the leaves with something like
[:map [:summer [:not [:= true]]]]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
:not 's generator could do this, but it's probably not the right place
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)
yep!
elsewhere -> it's a schema already defined elsewhere in code, being used in its exact form by rest of the system
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.
(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...)