Skip to content

Commit

Permalink
Merge pull request #1012 from frenchy64/explain-and-gen
Browse files Browse the repository at this point in the history
README: add dedicated sections for validating vector schemas and `:and` generation
  • Loading branch information
ikitommi authored Mar 6, 2024
2 parents 56ed9d1 + f88483f commit 68fa276
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 11 deletions.
100 changes: 89 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ Data-driven Schemas for Clojure/Script and [babashka](#babashka).
- [Generating values](#value-generation) from Schemas
- [Inferring Schemas](#inferring-schemas) from sample values and [Destructuring](#destructuring).
- Tools for [Programming with Schemas](#programming-with-schemas)
- [Parsing](#parsing-values), [Unparsing](#unparsing-values) and [Sequence Schemas](#sequence-schemas)
- [Parsing](#parsing-values) and [Unparsing](#unparsing-values) values
- [Sequence](#sequence-schemas), [Vector](#vector-schemas), and [Set](#set-schemas) Schemas
- [Persisting schemas](#persisting-schemas), even [function schemas](#serializable-functions)
- Immutable, Mutable, Dynamic, Lazy and Local [Schema Registries](#schema-registry)
- [Schema Transformations](#schema-Transformation) to [JSON Schema](#json-schema), [Swagger2](#swagger2), and [descriptions in english](#description)
Expand Down Expand Up @@ -379,26 +380,19 @@ default branching can be arbitrarily nested:

## Sequence schemas

You can use `:sequential` for any homogeneous Clojure sequence, `:vector` for vectors and `:set` for sets.
You can use `:sequential` to describe homogeneous sequential Clojure collections.

```clojure
(m/validate [:sequential any?] (list "this" 'is :number 42))
;; => true

(m/validate [:vector int?] [1 2 3])
(m/validate [:sequential int?] [42 105])
;; => true

(m/validate [:vector int?] (list 1 2 3))
(m/validate [:sequential int?] #{42 105})
;; => false
```

A `:tuple` describes a fixed length Clojure vector of heterogeneous elements:

```clojure
(m/validate [:tuple keyword? string? number?] [:bing "bang" 42])
;; => true
```

Malli also supports sequence regexes like [Seqexp](https://github.com/cgrand/seqexp) and Spec.
The supported operators are `:cat` & `:catn` for concatenation / sequencing

Expand Down Expand Up @@ -500,6 +494,54 @@ it is always better to use less general tools whenever possible:
(cc/quick-bench (valid? (range 10)))) ; Execution time mean : 0.12µs
```

## Vector schemas

You can use `:vector` to describe homogeneous Clojure vectors.

```clojure
(m/validate [:vector int?] [1 2 3])
;; => true

(m/validate [:vector int?] (list 1 2 3))
;; => false
```

A `:tuple` schema describes a fixed length Clojure vector of heterogeneous elements:

```clojure
(m/validate [:tuple keyword? string? number?] [:bing "bang" 42])
;; => true
```

To create a vector schema based on a seqex, use `:and`.

```clojure
;; non-empty vector starting with a keyword
(m/validate [:and [:cat :keyword [:* :any]]
vector?]
[:a 1])
; => true

(m/validate [:and [:cat :keyword [:* :any]]
vector?]
(:a 1))
; => false
```

Note: To generate values from a vector seqex, see [:and generation](#and-generation).

## Set schemas

You can use `:set` to describe homogeneous Clojure sets.

```clojure
(m/validate [:set int?] #{42 105})
;; => true

(m/validate [:set int?] #{:a :b})
;; => false
```

## String schemas

Using a predicate:
Expand Down Expand Up @@ -1873,6 +1915,42 @@ Integration with test.check:
; => (2 1 2 2 2 2 8 1 55 83)
```

### :and generation

Generators for `:and` schemas work by generating values from the first child, and then filtering
out any values that do not pass the overall `:and` schema.

For the most reliable results, place the schema that is most likely to generate valid
values for the entire schema as the first child of an `:and` schema.

```clojure
;; BAD: :string is unlikely to generate values satisfying the schema
(mg/generate [:and :string [:enum "a" "b" "c"]] {:seed 42})
; Execution error
; Couldn't satisfy such-that predicate after 100 tries.

;; GOOD: every value generated by the `:enum` is a string
(mg/generate [:and [:enum "a" "b" "c"] :string] {:seed 42})
; => "a"
```

You might need to customize the generator for the first `:and` child to improve
the chances of it generating valid values.

For example, a schema for non-empty heterogeneous vectors can validate values
by combining `:cat` and `vector?`, but since `:cat` generates sequences
we need to use `:gen/fmap` to make it generate vectors:

```clojure
;; generate a non-empty vector starting with a keyword
(mg/generate [:and [:cat {:gen/fmap vec}
:keyword [:* :any]]
vector?]
{:size 1
:seed 2})
;=> [:.+ [1]]
```

## Inferring schemas

Inspired by [F# Type providers](https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/type-providers/):
Expand Down
10 changes: 10 additions & 0 deletions test/malli/generator_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -909,3 +909,13 @@
(is (alphanumeric-string?
(mg/generate [:string {}]
{:seed seed})))))))

(deftest non-empty-vector-generator-test
(is (= [:.+ [1]]
(mg/generate [:and [:cat {:gen/fmap vec} :keyword [:* :any]] vector?]
{:size 1
:seed 2})))
(doseq [v (mg/sample [:and [:cat {:gen/fmap vec} :keyword [:* :any]] vector?]
{:seed 2})]
(is (vector? v))
(is (seq v))))

0 comments on commit 68fa276

Please sign in to comment.