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
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions docs/function-schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
* [Function Guards](#function-guards)
* [Generating Functions](#generating-functions)
* [Multi-arity Functions](#multi-arity-functions)
* [Polymorphic Functions](#polymorphic-functions)
* [Instrumentation](#instrumentation)
* [Instrumentation of Polymorphic Functions](#instrumentation-of-polymorphic-functions)
* [Flat Arrow Function Schemas](#flat-arrow-function-schemas)
* [Defn Schemas](#defn-schemas)
* [Defining Function Schemas](#defining-function-schemas)
Expand Down Expand Up @@ -65,7 +67,7 @@ Enter, function schemas.

## Function Schemas

Function values can be described with `:=>` and `:function` schemas. They allows description of both function arguments (as [sequence schemas](https://github.com/metosin/malli#sequence-schemas)) and function return values.
Function values can be described with `:=>`, `:function`, and `m/all` schemas. They allow descriptions of both function arguments (as [sequence schemas](https://github.com/metosin/malli#sequence-schemas)) and function return values.

Examples of function definitions:

Expand All @@ -91,11 +93,21 @@ Examples of function definitions:
[:function
[:=> [:cat :int] :int]
[:=> [:cat :int :int [:* :int]] :int]]

;; polymorphic identity function
(m/all [a] [:=> [:cat a] a])

;; polymorphic map function
(m/all [a b]
[:=> [:cat
[:=> [:cat a] b]
[:sequential a]]
[:sequential b]])
```

What is that `:cat` all about in the input schemas? Wouldn't it be simpler without it? Sure, check out [Flat Arrow Function Schema](#flat-arrow-function-schemas).

Function definition for the `plus` looks like this:
The schema for `plus` looks like this:

```clojure
(def =>plus [:=> [:cat :int :int] :int])
Expand Down Expand Up @@ -319,11 +331,29 @@ Generating multi-arity functions:
; => -2326
```

### Polymorphic Functions

A polymorphic function using `m/all` is generatively tested by instantiating schema variables with small schemas.

For example, the polymorphic identity schema

```clojure
(m/all [a] [:=> [:cat a] a])
```

is generatively tested with schemas like

```clojure
[:=> [:cat :nil] :nil]
[:=> [: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.

```

### Instrumentation

Besides testing function schemas as values, we can also instrument functions to enable runtime validation of arguments and return values.

Simplest way to do this is to use `m/-instrument` which takes an options map and a function and returns an instrumented function. Valid options include:
The simplest way to do this is to use `m/-instrument` which takes an options map and a function and returns an instrumented function. Valid options include:

| key | description |
| ----------|-------------|
Expand Down Expand Up @@ -397,6 +427,25 @@ With `:gen` we can omit the function body. Here's an example to generate random
; =throws=> :malli.core/invalid-arity {:arity 3, :arities #{1 2}, :args (10 20 30), :input nil, :schema [:function [:=> [:cat :int] [:int {:max 6}]] [:=> [:cat :int :int] [:int {:max 6}]]]}
```

### Instrumentation of Polymorphic Functions

A polymorphic function will be instrumented as if all its schema variables were instantiated with
their upper bounds, usually `:any`. The instrumented schema is calculated via `m/deref`.

Schema variables by default do not allow regex splicing, so instantiations are wrapped in `:schema`.

```clojure
(-> (m/all [a] [:=> [:cat a] a]) m/deref)
;=> [:=> [:cat [:schema :any]] [:schema :any]]

(def options {:registry (mr/composite-registry m/default-registry (mu/schemas))})

(-> (m/all [[M [:maybe :map]] X] [:=> [:cat M X] [:merge M [:map [:x X]]]])
(m/schema options)
m/deref)
;=> [:=> [:cat [:schema [:maybe :map]] [:schema :any]]
; [:merge [:schema [:maybe :map]] [:map [:x [:schema :any]]]]]

### Flat Arrow Function Schemas

Function schema `:=>` requires input arguments to be wrapped in `:cat` or `:catn`. Since `0.16.2` there is also flat arrow schema: `:->` that allows input schema to be defined as flat sequence:
Expand Down
Loading