Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polymorphic schemas #1053

Closed
wants to merge 19 commits into from
Closed

Polymorphic schemas #1053

wants to merge 19 commits into from

Conversation

frenchy64
Copy link
Collaborator

@frenchy64 frenchy64 commented May 2, 2024

Close #846
Depends on #1071

I went through several iterations on the internal representation. It felt correct once the serialized form of a polymorphic schema was human readable and ergonomic (doesn't require quoting to evaluate correctly, so no symbols allowed). The tradeoff is that we need to implement capture-avoiding substitution, and that's very error-prone. I tested a few cases.

The identity schema's form is [:all [:X] [:-> :X :X]]. It's introduced with the m/all macro by (m/all [X] [:-> X X]).

To instantiate it, you perform a walk/postwalk-replace with {:X this-to-instantiate}.

(m/inst (m/all [X] [:-> X X])
        [:int])
;=> [:-> [:schema :int] [:schema :int]]

The extra :schema's make sure regex's don't splice. By default, variables are single schemas.

This representation is delicate, but works and is serializable if a few rules are followed:

It's a leaf schema, and it's essential that its body is not manipulated by users before instantiating it.

For example, variables are renamed if they clash with any keywords in the body:

(m/all [cat] [:=> [:cat cat] cat])
;=> [:all [:cat0] [:=> [:cat :cat0] :cat0]]

m/inst also does renaming to avoid capture. A variable can be "captured" by any schema, not just another variable, since variables are keywords and so are schemas.

The following renames to :y0 because if it was :y, then instantiation would destroy the body via (postwalk-replace {:y ...}).

(m/inst (m/all [x] (m/all [y] x))
        [(m/all [y] y)])
;=> [:all [:y0] [:schema [:all [:y] :y]]]

I don't know how to generate polymorphic functions, but there's some ideas in my deterministic function PR #1042

The mg/check implementation is pretty simple and can be improved. It just instantiates variables to very small schemas of a single value.

@frenchy64 frenchy64 marked this pull request as ready for review July 16, 2024 05:27
Comment on lines 348 to 349
[:=> [:cat [:enum 50]] [:enum 50]]
[:=> [:cat [:enum 5333344553]] [:enum 5333344553]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you have three different schemas here? I know the two enums are technically different, but maybe :nil, [:enum 50] and [:map-of :keyword :any] or something would better clarify what's happening.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it really is this simple, more like:

[:fn {:gen/return nil} (fn [r] (= nil r))]
[:fn {:gen/return 50} (fn [r] (= 50 r))]
[:fn {:gen/return 5333344553} (fn [r] (= 5333344553 r))]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting, okay.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To go further we'll need to think about what it means to shrink a schema. There are ideas in the spec impl of this, but this is the minimal proof-of-concept.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will elaborate on exactly what happens in the docs though, thanks for the feedback.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found the reason I needed such a convoluted representation in the first place: #1070

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will elaborate on exactly what happens in the docs though, thanks for the feedback.

Done.

src/malli/core.cljc Outdated Show resolved Hide resolved
@frenchy64 frenchy64 marked this pull request as draft July 18, 2024 02:53
@frenchy64
Copy link
Collaborator Author

Moved to frenchy64#11

@frenchy64 frenchy64 closed this Jul 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Schema variables, aka generics
2 participants