lawspec 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -15
- package/bin/lawspec.mjs +2 -2
- package/build.json +10 -10
- package/core.wasm +0 -0
- package/examples/specs/algebra.lawspec +254 -0
- package/examples/specs/currying.lawspec +60 -0
- package/index.d.ts +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**State the law once. Check it everywhere.**
|
|
4
4
|
|
|
5
|
-
LawSpec 0.
|
|
5
|
+
LawSpec 0.6 compiles reusable laws into native property tests, executable examples,
|
|
6
6
|
and implementation adapters. The compiler is Haskell, distributed as prebuilt
|
|
7
7
|
WebAssembly with a Node CLI and an asynchronous, typed JavaScript API.
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@ WebAssembly with a Node CLI and an asynchronous, typed JavaScript API.
|
|
|
11
11
|
Install [LawSpec from npm](https://www.npmjs.com/package/lawspec):
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
npm install --save-dev lawspec@0.
|
|
14
|
+
npm install --save-dev lawspec@0.6.0
|
|
15
15
|
npx lawspec --version
|
|
16
16
|
```
|
|
17
17
|
|
|
@@ -25,8 +25,8 @@ local dependencies so LawSpec can create its `package.json` and test script:
|
|
|
25
25
|
```sh
|
|
26
26
|
mkdir lawspec-example
|
|
27
27
|
cd lawspec-example
|
|
28
|
-
npm exec --package=lawspec@0.
|
|
29
|
-
npm install --save-dev lawspec@0.
|
|
28
|
+
npm exec --package=lawspec@0.6.0 -- lawspec init --target javascript
|
|
29
|
+
npm install --save-dev lawspec@0.6.0
|
|
30
30
|
npx lawspec check
|
|
31
31
|
npx lawspec explain 'example.atoi_codec::itoa and then atoi yields a'
|
|
32
32
|
npx lawspec doctor
|
|
@@ -61,7 +61,7 @@ properties with the selected framework's shrinking and failure reporting.
|
|
|
61
61
|
| `kotlin` | JDK/JVM 25, Gradle 9.1–9.3, Kotlin 2.3.21 | Kotest 5.9.1 | `gradle test` |
|
|
62
62
|
|
|
63
63
|
Java 25 and Python 3.13 are the minimum baselines. New JVM releases are admitted
|
|
64
|
-
through compatibility profiles after testing; v0.
|
|
64
|
+
through compatibility profiles after testing; v0.6's current JVM profile certifies
|
|
65
65
|
25. Python templates declare `requires-python = ">=3.13"` and runtime checks
|
|
66
66
|
currently recognize 3.13 and 3.14. Kotlin templates pin Gradle's supported build
|
|
67
67
|
configuration to Kotlin 2.3.21 and target JVM 25.
|
|
@@ -161,10 +161,10 @@ Reusable laws can declare typed unary function parameters and `requires Eq a`.
|
|
|
161
161
|
Definitions support law application, function application/composition, universal
|
|
162
162
|
quantification, `implies`, Boolean predicates, scalar literals, and equality.
|
|
163
163
|
Function signatures use `Int32`, `Text`, and `Bool`; generic variables are
|
|
164
|
-
supported in reusable laws. v0.
|
|
164
|
+
supported in reusable laws. v0.6 generates quantified inputs of all three types,
|
|
165
165
|
including mixed and multiple inputs. These types can also be intermediate or
|
|
166
|
-
compared results. Functions are synchronous
|
|
167
|
-
|
|
166
|
+
compared results. Functions are synchronous and support curried signatures with any positive
|
|
167
|
+
number of scalar arguments. Text literals are double-quoted, with escapes such as `\"`, `\\`,
|
|
168
168
|
`\n`, and `\t`; examples must bind each input to a literal of its declared type.
|
|
169
169
|
Text values contain Unicode scalar values; surrogate code points are rejected.
|
|
170
170
|
|
|
@@ -179,6 +179,140 @@ Additional primitives, external law packages, cross-unit imports beyond the
|
|
|
179
179
|
prelude, async functions, direct existing-symbol binding and browser hosting are
|
|
180
180
|
outside this release.
|
|
181
181
|
|
|
182
|
+
## Algebra and currying (0.6)
|
|
183
|
+
|
|
184
|
+
Version 0.6 adds algebra laws, scalar law parameters, curried signatures,
|
|
185
|
+
and conjunctions.
|
|
186
|
+
|
|
187
|
+
```lawspec
|
|
188
|
+
unit example.addition
|
|
189
|
+
|
|
190
|
+
add :: Int32 -> Int32 -> Int32
|
|
191
|
+
|
|
192
|
+
law `addition commutes` is
|
|
193
|
+
definition is
|
|
194
|
+
`commutative` add
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
example `3 plus 5 and 5 plus 3 both produce 8` is
|
|
198
|
+
x = 3
|
|
199
|
+
y = 5
|
|
200
|
+
expect add x y = 8
|
|
201
|
+
expect add y x = 8
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
law `zero is an identity on both sides` is
|
|
206
|
+
definition is
|
|
207
|
+
`identity` add 0
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
example `zero preserves 3 on either side` is
|
|
211
|
+
x = 3
|
|
212
|
+
expect add 0 x = 3
|
|
213
|
+
expect add x 0 = 3
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Arrows associate to the right and application associates to the left:
|
|
219
|
+
`f :: a -> b -> c` takes two arguments, and `f x y` means `(f x) y`.
|
|
220
|
+
A partial application such as `add 1` can be passed to a reusable unary law;
|
|
221
|
+
`sumFour 1 2` can be passed to a binary law. Partial applications also compose.
|
|
222
|
+
The compiler specializes these expressions before emission. Java, Kotlin,
|
|
223
|
+
Python, JavaScript, TypeScript and Go adapters take ordinary positional arguments
|
|
224
|
+
(`add(x, y)`); Haskell adapters use native currying (`add x y`). Argument order
|
|
225
|
+
and types are preserved, including mixtures of `Text`, `Bool` and `Int32`.
|
|
226
|
+
|
|
227
|
+
Law parameters can also be scalar values: `(e :: a)` supplies an identity and
|
|
228
|
+
`(zero :: a)` supplies an absorbing element. Pass literals directly, for example
|
|
229
|
+
`left identity` with arguments `add 0`, or `absorbing element` with `multiply 0`.
|
|
230
|
+
Functions declared by a unit take one or more scalar inputs and return a scalar;
|
|
231
|
+
reusable laws accept these curried functions, their partial applications, and
|
|
232
|
+
scalar parameters. Quantified test inputs remain scalar.
|
|
233
|
+
|
|
234
|
+
The prelude defines the following laws. Every row has an executable example in
|
|
235
|
+
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.6.0/examples/specs/algebra.lawspec), including both sides of every
|
|
236
|
+
combined law. `f` and `g` are binary operations, `inverse` is unary, and `e` and
|
|
237
|
+
`zero` are scalar parameters. All these laws require equality of the element type.
|
|
238
|
+
|
|
239
|
+
| Law and arguments | Equations checked for every quantified input |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `commutative f` | `f x y = f y x` |
|
|
242
|
+
| `associative f` | `f (f x y) z = f x (f y z)` |
|
|
243
|
+
| `left identity f e` | `f e x = x` |
|
|
244
|
+
| `right identity f e` | `f x e = x` |
|
|
245
|
+
| `identity f e` | Both identity equations |
|
|
246
|
+
| `left absorbing element f zero` | `f zero x = zero` |
|
|
247
|
+
| `right absorbing element f zero` | `f x zero = zero` |
|
|
248
|
+
| `absorbing element f zero` | Both absorbing equations |
|
|
249
|
+
| `left distributive f g` | `f x (g y z) = g (f x y) (f x z)` |
|
|
250
|
+
| `right distributive f g` | `f (g x y) z = g (f x z) (f y z)` |
|
|
251
|
+
| `distributive f g` | Both distributive equations |
|
|
252
|
+
| `idempotent operation f` | `f x x = x` (the existing `idempotent` law is unary) |
|
|
253
|
+
| `left inverse element f inverse e` | `f (inverse x) x = e` |
|
|
254
|
+
| `right inverse element f inverse e` | `f x (inverse x) = e` |
|
|
255
|
+
| `invertible f inverse e` | Both inverse equations |
|
|
256
|
+
| `left division f divideLeft` | `f x (divideLeft x y) = y` and `divideLeft x (f x y) = y` |
|
|
257
|
+
| `right division f divideRight` | `f (divideRight x y) y = x` and `divideRight (f x y) y = x` |
|
|
258
|
+
| `divisible f divideLeft divideRight` | All four division equations |
|
|
259
|
+
| `involution f` | `f (f x) = x` |
|
|
260
|
+
|
|
261
|
+
Here **divisible** means algebraic left/right division. `divideLeft x y` solves
|
|
262
|
+
`f x result = y`; `divideRight x y` solves `f result y = x`. The subtraction
|
|
263
|
+
example deliberately uses a noncommutative operation: with `x = 3` and `y = 5`,
|
|
264
|
+
the left solution is `-2` and the right solution is `8`. Recovery is checked in
|
|
265
|
+
both directions. These are total laws; a partially defined division needs an
|
|
266
|
+
explicit domain predicate and conditional equations.
|
|
267
|
+
|
|
268
|
+
`invertible` checks the supplied inverse operation. Check `identity` and
|
|
269
|
+
`associative` as well when specifying a group. The prelude states contracts;
|
|
270
|
+
it does not supply arithmetic implementations or prove a structure from random
|
|
271
|
+
tests. The numeric examples use Int32 arithmetic modulo 2^32 so their laws hold
|
|
272
|
+
at overflow boundaries on every target. JavaScript uses `Math.imul` for products,
|
|
273
|
+
and Python explicitly wraps results into the signed Int32 range in the test
|
|
274
|
+
adapters.
|
|
275
|
+
|
|
276
|
+
Use `and` to require multiple conclusions in one law. For example:
|
|
277
|
+
|
|
278
|
+
```lawspec
|
|
279
|
+
unit example.absorption
|
|
280
|
+
multiply :: Int32 -> Int32 -> Int32
|
|
281
|
+
|
|
282
|
+
law `zero absorbs on both sides` is
|
|
283
|
+
definition is
|
|
284
|
+
`for all` (x :: Int32) .
|
|
285
|
+
multiply 0 x = 0 and multiply x 0 = 0
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
example `3 times zero and zero times 3 both produce zero` is
|
|
289
|
+
x = 3
|
|
290
|
+
expect multiply 0 x = 0
|
|
291
|
+
expect multiply x 0 = 0
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Quantification and implication extend through the following conjunction:
|
|
297
|
+
`p x implies A and B` guards both conclusions. Write `(p x implies A) and B`
|
|
298
|
+
to guard only the first. A shared guard runs once per check; false guards skip
|
|
299
|
+
their entire consequence. Every conjunct is type-checked and emitted. As with
|
|
300
|
+
existing assertions, the first failure stops that individual test. `and` is now
|
|
301
|
+
a reserved word.
|
|
302
|
+
|
|
303
|
+
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.6.0/examples/specs/currying.lawspec) demonstrate a four-argument
|
|
304
|
+
function partially applied twice, a formatter with four heterogeneous arguments,
|
|
305
|
+
and composition after partial application. Each example states its exact outputs.
|
|
306
|
+
Run `node npm/bin/lawspec.mjs examples` after rebuilding to inspect all nine units
|
|
307
|
+
in all seven target languages (126 artifacts).
|
|
308
|
+
|
|
309
|
+
The expanded API's **`assertion` tree is authoritative**: `AssertEqual` contains
|
|
310
|
+
two expressions, `AssertImplies` contains a condition and consequence, and
|
|
311
|
+
`AssertAll` contains every conjunct. Existing `left`, `right`, and `guards`
|
|
312
|
+
fields are compatibility projections of the first conclusion only; consumers
|
|
313
|
+
checking compound laws must traverse `assertion`. Source definitions add `And`.
|
|
314
|
+
`lawspec explain` prints the full conjunction and its conditional scope.
|
|
315
|
+
|
|
182
316
|
## Predicates and conditional laws (0.5)
|
|
183
317
|
|
|
184
318
|
A predicate is a unary function returning `Bool`. Use `true` and `false` in
|
|
@@ -218,7 +352,7 @@ law `valid ports round trip` is
|
|
|
218
352
|
end
|
|
219
353
|
```
|
|
220
354
|
|
|
221
|
-
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
355
|
+
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.6.0/examples/specs/parse_port.lawspec)
|
|
222
356
|
defines valid ports as 1–65535, and covers both endpoints, ordinary ports, zero,
|
|
223
357
|
negative values, and 65536. All explicit `expect` assertions run regardless of
|
|
224
358
|
the law's condition. A false condition skips only the consequence: invalid ports
|
|
@@ -236,7 +370,7 @@ The prelude includes `satisfies predicate` (the predicate holds for every input)
|
|
|
236
370
|
and `left inverse when predicate parse render` (the guarded round trip above).
|
|
237
371
|
These reusable laws preserve the condition and its lexical bindings when expanded.
|
|
238
372
|
`equivalent` can also compare two predicates, since `Bool` supports equality.
|
|
239
|
-
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
373
|
+
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.6.0/examples/specs/boolean_flags.lawspec)
|
|
240
374
|
checks that flipping twice restores both `false` and `true`; all targets generate
|
|
241
375
|
Boolean property inputs and explicit tests for both Boolean boundary values. Java caps Boolean-only JetCheck runs at the number of
|
|
242
376
|
possible input combinations (up to 100), avoiding generator exhaustion.
|
|
@@ -315,7 +449,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
|
|
|
315
449
|
The example inherits the input name `x` from the prelude. Both functions are
|
|
316
450
|
user-owned adapter functions; either may delegate to your existing code.
|
|
317
451
|
|
|
318
|
-
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
452
|
+
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.6.0/examples/specs/equivalent.lawspec) compares decimal
|
|
319
453
|
renderers and two implementations that clamp negative integers to zero. For
|
|
320
454
|
JavaScript, their adapters can be:
|
|
321
455
|
|
|
@@ -352,7 +486,7 @@ law `normalizers agree` is
|
|
|
352
486
|
end
|
|
353
487
|
```
|
|
354
488
|
|
|
355
|
-
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
489
|
+
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.6.0/examples/specs/slug.lawspec)
|
|
356
490
|
compares two implementations of ASCII-space replacement. It includes empty,
|
|
357
491
|
Unicode and escaped text. Each target uses its native string generator:
|
|
358
492
|
JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
|
|
@@ -379,7 +513,7 @@ law `canonicalization reaches a fixed point` is
|
|
|
379
513
|
end
|
|
380
514
|
```
|
|
381
515
|
|
|
382
|
-
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
516
|
+
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.6.0/examples/specs/canonical_url.lawspec)
|
|
383
517
|
uses removal of **all trailing slashes** as a small fixed-point demonstration,
|
|
384
518
|
not a complete URL canonicalization algorithm. For JavaScript:
|
|
385
519
|
|
|
@@ -388,7 +522,7 @@ export const canonicalize = value => value.replace(/\/+$/, "");
|
|
|
388
522
|
```
|
|
389
523
|
|
|
390
524
|
Removing just one trailing slash fails the supplied repeated-slash example.
|
|
391
|
-
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
525
|
+
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.6.0/examples/specs/mixed_inputs.lawspec)
|
|
392
526
|
shows `Text` and `Int32` in the same quantified property and executable example.
|
|
393
527
|
The JavaScript API represents input bindings and expected values as `number | string | boolean`.
|
|
394
528
|
Each example includes `expectations: { actual: Expr; expected: number | string | boolean }[]`.
|
|
@@ -453,7 +587,7 @@ by the JS shim.
|
|
|
453
587
|
## Build and verify
|
|
454
588
|
|
|
455
589
|
For contributors working from a repository checkout, build a local archive with
|
|
456
|
-
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.
|
|
590
|
+
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.6.0.tgz`.
|
|
457
591
|
The package payload lives in `npm/`.
|
|
458
592
|
|
|
459
593
|
```sh
|
package/bin/lawspec.mjs
CHANGED
|
@@ -185,13 +185,13 @@ function explainExamples(law) {
|
|
|
185
185
|
async function main() {
|
|
186
186
|
if (!verb || ["help", "--help", "-h"].includes(verb)) {
|
|
187
187
|
output(
|
|
188
|
-
"LawSpec 0.
|
|
188
|
+
"LawSpec 0.6.0\nUsage: lawspec init --target <language> [--project <directory>]\n lawspec check | doctor | explain <unit>::<law> | generate\n lawspec examples [--target <language>] [--output example_artifacts]\nOptions: --config <path>, --target <language>, --json\nGeneration: --dry-run, --check\nTargets: " +
|
|
189
189
|
targets.join(", "),
|
|
190
190
|
);
|
|
191
191
|
return;
|
|
192
192
|
}
|
|
193
193
|
if (verb === "--version") {
|
|
194
|
-
output("0.
|
|
194
|
+
output("0.6.0");
|
|
195
195
|
return;
|
|
196
196
|
}
|
|
197
197
|
if (positional.length > (verb === "explain" ? 1 : 0))
|
package/build.json
CHANGED
|
@@ -17,23 +17,23 @@
|
|
|
17
17
|
"wasm/lawspec-wasm.cabal"
|
|
18
18
|
],
|
|
19
19
|
"digests": {
|
|
20
|
-
"package.yaml": "
|
|
20
|
+
"package.yaml": "25d79d741039fe21e1164d789fdf4f698f135df7e4c96c82560e4f7efe8fbdf9",
|
|
21
21
|
"src/LawSpec/Api.hs": "00b1e4bc81231ebd387f10652463904084ebf9c3c4486fd2c2217a6d4dbfc157",
|
|
22
|
-
"src/LawSpec/Compile.hs": "
|
|
23
|
-
"src/LawSpec/Emit.hs": "
|
|
24
|
-
"src/LawSpec/Gen.hs": "
|
|
25
|
-
"src/LawSpec/Model.hs": "
|
|
26
|
-
"src/LawSpec/Parser.hs": "
|
|
27
|
-
"src/LawSpec/Prelude.hs": "
|
|
22
|
+
"src/LawSpec/Compile.hs": "64aa042ce2f0f460d691ea43fa27bf75576a49a6e852a068f62a2cf5a28e81b7",
|
|
23
|
+
"src/LawSpec/Emit.hs": "85ab85d789c4e2c98703e2768075cb1c0bed42b987c45b5060b9a9df56e8d291",
|
|
24
|
+
"src/LawSpec/Gen.hs": "c596e411790a47cb15b2945d391b9909cbec1972bf6e502ed8c069db2c67e991",
|
|
25
|
+
"src/LawSpec/Model.hs": "549f553d1cc5d65c804a5acfac0cc06e63ff10eb80674893e32fcd3c0a71a5b3",
|
|
26
|
+
"src/LawSpec/Parser.hs": "ae7c5ad29871b99441db2dae10548afafb2ae4931dd1a62208ca0f5e3545002c",
|
|
27
|
+
"src/LawSpec/Prelude.hs": "b1e155c12f58e7c346551a0c65c5d5af76d045b3f1625977ca2a5d1c97cb8c46",
|
|
28
28
|
"stack.yaml": "20ccf4d599e355e60b7aa4f814a7cd4299fe2048616cc2e6dbdc22a7bd8cec73",
|
|
29
29
|
"stack.yaml.lock": "ae222b9c81af920c56e50fa4596fa57786e7fa5a7b461390362b2b2ff63818c7",
|
|
30
30
|
"wasm/app/Exports.hs": "4ecbdac8faa2449e6fc61b93c82e6278fa14374154f29b4dacf0eef433f06a47",
|
|
31
31
|
"wasm/cabal.project": "021e560afdc5eb4cb7169e7119ecb8f92c9ee170245af909c94b612941bff5cc",
|
|
32
32
|
"wasm/cabal.project.freeze": "733dbed3d2ecccb26e874fd58f136296dad772184deb1658c196b5d54a0814dc",
|
|
33
|
-
"wasm/lawspec-wasm.cabal": "
|
|
34
|
-
"npm/core.wasm": "
|
|
33
|
+
"wasm/lawspec-wasm.cabal": "d0d9c395b96de75264d5dc62f7995f02b285aebc696a2cf603a62b4e280f9132",
|
|
34
|
+
"npm/core.wasm": "e90e71610ca9f58a1b1e88d65f593c74d2a5b09347a4b47802bd771c954b7e42",
|
|
35
35
|
"npm/core_jsffi.js": "88d136efe92f7cff5758c8fec8d9b6bbc9707fe37741cebeeb415fe34ce3d72b",
|
|
36
36
|
"npm/api.mjs": "d6df654600172131ac66a55b86876fc29cde2a76a0a63bbefff3183488a4837b",
|
|
37
|
-
"npm/index.d.ts": "
|
|
37
|
+
"npm/index.d.ts": "628016928dc71dc735d59d15ccde45500d9a4133e3b20c8d3c21cb9c65ccb604"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/core.wasm
CHANGED
|
Binary file
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
unit example.algebra
|
|
2
|
+
|
|
3
|
+
-- Int32 arithmetic in this example wraps modulo 2^32 on every target.
|
|
4
|
+
add :: Int32 -> Int32 -> Int32
|
|
5
|
+
multiply :: Int32 -> Int32 -> Int32
|
|
6
|
+
negateValue :: Int32 -> Int32
|
|
7
|
+
maximumValue :: Int32 -> Int32 -> Int32
|
|
8
|
+
subtractValue :: Int32 -> Int32 -> Int32
|
|
9
|
+
divideLeft :: Int32 -> Int32 -> Int32
|
|
10
|
+
divideRight :: Int32 -> Int32 -> Int32
|
|
11
|
+
|
|
12
|
+
law `addition is commutative` is
|
|
13
|
+
definition is
|
|
14
|
+
`commutative` add
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
example `3 plus 5 and 5 plus 3 both produce 8` is
|
|
18
|
+
x = 3
|
|
19
|
+
y = 5
|
|
20
|
+
expect add x y = 8
|
|
21
|
+
expect add y x = 8
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
law `addition is associative` is
|
|
26
|
+
definition is
|
|
27
|
+
`associative` add
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
example `both groupings of 3 plus 5 plus 7 produce 15` is
|
|
31
|
+
x = 3
|
|
32
|
+
y = 5
|
|
33
|
+
z = 7
|
|
34
|
+
expect add (add x y) z = 15
|
|
35
|
+
expect add x (add y z) = 15
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
law `zero is left identity for addition` is
|
|
40
|
+
definition is
|
|
41
|
+
`left identity` add 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
example `adding zero on left preserves 3` is
|
|
45
|
+
x = 3
|
|
46
|
+
expect add 0 x = 3
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
law `zero is right identity for addition` is
|
|
51
|
+
definition is
|
|
52
|
+
`right identity` add 0
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
example `adding zero on right preserves 3` is
|
|
56
|
+
x = 3
|
|
57
|
+
expect add x 0 = 3
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
law `zero is two-sided identity for addition` is
|
|
62
|
+
definition is
|
|
63
|
+
`identity` add 0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
example `adding zero on both sides preserves 3` is
|
|
67
|
+
x = 3
|
|
68
|
+
expect add 0 x = 3
|
|
69
|
+
expect add x 0 = 3
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
law `zero absorbs multiplication left` is
|
|
74
|
+
definition is
|
|
75
|
+
`left absorbing element` multiply 0
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
example `zero on left of multiplication produces zero` is
|
|
79
|
+
x = 3
|
|
80
|
+
expect multiply 0 x = 0
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
law `zero absorbs multiplication right` is
|
|
85
|
+
definition is
|
|
86
|
+
`right absorbing element` multiply 0
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
example `zero on right of multiplication produces zero` is
|
|
90
|
+
x = 3
|
|
91
|
+
expect multiply x 0 = 0
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
law `zero absorbs multiplication on both sides` is
|
|
96
|
+
definition is
|
|
97
|
+
`absorbing element` multiply 0
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
example `zero on both sides of multiplication produces zero` is
|
|
101
|
+
x = 3
|
|
102
|
+
expect multiply 0 x = 0
|
|
103
|
+
expect multiply x 0 = 0
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
law `multiplication distributes over addition left` is
|
|
108
|
+
definition is
|
|
109
|
+
`left distributive` multiply add
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
example `left distribution produces 36` is
|
|
113
|
+
x = 3
|
|
114
|
+
y = 5
|
|
115
|
+
z = 7
|
|
116
|
+
expect multiply x (add y z) = 36
|
|
117
|
+
expect add (multiply x y) (multiply x z) = 36
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
law `multiplication distributes over addition right` is
|
|
122
|
+
definition is
|
|
123
|
+
`right distributive` multiply add
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
example `right distribution produces 56` is
|
|
127
|
+
x = 3
|
|
128
|
+
y = 5
|
|
129
|
+
z = 7
|
|
130
|
+
expect multiply (add x y) z = 56
|
|
131
|
+
expect add (multiply x z) (multiply y z) = 56
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
law `multiplication distributes over addition on both sides` is
|
|
136
|
+
definition is
|
|
137
|
+
`distributive` multiply add
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
example `left distribution produces 36; right distribution produces 56` is
|
|
141
|
+
x = 3
|
|
142
|
+
y = 5
|
|
143
|
+
z = 7
|
|
144
|
+
expect multiply x (add y z) = 36
|
|
145
|
+
expect add (multiply x y) (multiply x z) = 36
|
|
146
|
+
expect multiply (add x y) z = 56
|
|
147
|
+
expect add (multiply x z) (multiply y z) = 56
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
law `maximum is an idempotent operation` is
|
|
152
|
+
definition is
|
|
153
|
+
`idempotent operation` maximumValue
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
example `the maximum of 3 and itself is 3` is
|
|
157
|
+
x = 3
|
|
158
|
+
expect maximumValue x x = 3
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
law `negation supplies left additive inverses` is
|
|
163
|
+
definition is
|
|
164
|
+
`left inverse element` add negateValue 0
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
example `3 has inverse -3; the left inverse equation produces zero` is
|
|
168
|
+
x = 3
|
|
169
|
+
expect negateValue x = -3
|
|
170
|
+
expect add (negateValue x) x = 0
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
law `negation supplies right additive inverses` is
|
|
175
|
+
definition is
|
|
176
|
+
`right inverse element` add negateValue 0
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
example `3 has inverse -3; the right inverse equation produces zero` is
|
|
180
|
+
x = 3
|
|
181
|
+
expect negateValue x = -3
|
|
182
|
+
expect add x (negateValue x) = 0
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
law `negation supplies both additive inverses` is
|
|
187
|
+
definition is
|
|
188
|
+
`invertible` add negateValue 0
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
example `3 has inverse -3; the left and right inverse equation produces zero` is
|
|
192
|
+
x = 3
|
|
193
|
+
expect negateValue x = -3
|
|
194
|
+
expect add (negateValue x) x = 0
|
|
195
|
+
expect add x (negateValue x) = 0
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
law `subtraction has left division` is
|
|
200
|
+
definition is
|
|
201
|
+
`left division` subtractValue divideLeft
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
example `left solution is -2 since 3 - (-2) = 5; reversing the operation recovers 5` is
|
|
205
|
+
x = 3
|
|
206
|
+
y = 5
|
|
207
|
+
expect divideLeft x y = -2
|
|
208
|
+
expect subtractValue x (divideLeft x y) = 5
|
|
209
|
+
expect divideLeft x (subtractValue x y) = 5
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
law `subtraction has right division` is
|
|
214
|
+
definition is
|
|
215
|
+
`right division` subtractValue divideRight
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
example `right solution is 8 since 8 - 5 = 3; reversing the operation recovers 3` is
|
|
219
|
+
x = 3
|
|
220
|
+
y = 5
|
|
221
|
+
expect divideRight x y = 8
|
|
222
|
+
expect subtractValue (divideRight x y) y = 3
|
|
223
|
+
expect divideRight (subtractValue x y) y = 3
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
law `subtraction has divisible` is
|
|
228
|
+
definition is
|
|
229
|
+
`divisible` subtractValue divideLeft divideRight
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
example `left solution is -2; right solution is 8; both recovery directions hold` is
|
|
233
|
+
x = 3
|
|
234
|
+
y = 5
|
|
235
|
+
expect divideLeft x y = -2
|
|
236
|
+
expect subtractValue x (divideLeft x y) = 5
|
|
237
|
+
expect divideLeft x (subtractValue x y) = 5
|
|
238
|
+
expect divideRight x y = 8
|
|
239
|
+
expect subtractValue (divideRight x y) y = 3
|
|
240
|
+
expect divideRight (subtractValue x y) y = 3
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
law `negation is an involution` is
|
|
245
|
+
definition is
|
|
246
|
+
`involution` negateValue
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
example `negating -3 twice restores -3` is
|
|
250
|
+
x = -3
|
|
251
|
+
expect negateValue x = 3
|
|
252
|
+
expect negateValue (negateValue x) = -3
|
|
253
|
+
end
|
|
254
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
unit example.currying
|
|
2
|
+
|
|
3
|
+
sumFour :: Int32 -> Int32 -> Int32 -> Int32 -> Int32
|
|
4
|
+
format :: Text -> Bool -> Int32 -> Text -> Text
|
|
5
|
+
referenceFormat :: Text -> Bool -> Int32 -> Text -> Text
|
|
6
|
+
trim :: Text -> Text
|
|
7
|
+
|
|
8
|
+
law `a four-argument function partially applied twice is commutative` is
|
|
9
|
+
definition is
|
|
10
|
+
`commutative` (sumFour 1 2)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
example `the fixed 1 and 2 plus 3 and 4 sum to 10 in either order` is
|
|
14
|
+
x = 3
|
|
15
|
+
y = 4
|
|
16
|
+
expect sumFour 1 2 x y = 10
|
|
17
|
+
expect sumFour 1 2 y x = 10
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
law `staged formatter`
|
|
22
|
+
(render :: Int32 -> Text -> Text)
|
|
23
|
+
(reference :: Int32 -> Text -> Text)
|
|
24
|
+
is
|
|
25
|
+
definition is
|
|
26
|
+
`for all` (port :: Int32) (suffix :: Text) .
|
|
27
|
+
render port suffix = reference port suffix
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
law `partial application preserves heterogeneous argument order` is
|
|
32
|
+
definition is
|
|
33
|
+
`staged formatter` (format "port:" true) (referenceFormat "port:" true)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
example `port 443 with a tcp suffix formats as port:443/tcp` is
|
|
37
|
+
port = 443
|
|
38
|
+
suffix = "/tcp"
|
|
39
|
+
expect format "port:" true port suffix = "port:443/tcp"
|
|
40
|
+
expect referenceFormat "port:" true port suffix = "port:443/tcp"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
example `a disabled numeric field keeps the prefix and suffix` is
|
|
44
|
+
port = 443
|
|
45
|
+
suffix = "/tcp"
|
|
46
|
+
expect format "port:" false port suffix = "port:/tcp"
|
|
47
|
+
expect referenceFormat "port:" false port suffix = "port:/tcp"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
law `composition accepts a partially applied function` is
|
|
52
|
+
definition is
|
|
53
|
+
`equivalent` ((format "port:" true 443) . trim) ((referenceFormat "port:" true 443) . trim)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
example `trim runs before the remaining formatter argument` is
|
|
57
|
+
x = " /tcp "
|
|
58
|
+
expect ((format "port:" true 443) . trim) x = "port:443/tcp"
|
|
59
|
+
end
|
|
60
|
+
end
|
package/index.d.ts
CHANGED
|
@@ -6,11 +6,12 @@ export interface Diagnostic { code: string; message: string; at: Location | null
|
|
|
6
6
|
export interface Artifact { path: string; content: string; ownership: 'user' | 'generated' }
|
|
7
7
|
export type Type = {tag: 'Named' | 'Variable'; contents: string} | {tag: 'Arrow'; contents: [Type, Type]};
|
|
8
8
|
export type Expr = {tag: 'Var' | 'StringLit'; contents: string} | {tag: 'Number'; contents: number} | {tag: 'BoolLit'; contents: boolean} | {tag: 'Apply' | 'Compose'; contents: [Expr, Expr]};
|
|
9
|
-
export type Definition = {tag: 'Forall'; contents: [[string, Type][], Definition]} | {tag: 'Equal'; contents: [Expr, Expr]} | {tag: 'Holds'; contents: Expr} | {tag: 'Implies'; contents: [Expr, Definition]} | {tag: 'Invoke'; contents: [string, Expr[]]};
|
|
9
|
+
export type Definition = {tag: 'Forall'; contents: [[string, Type][], Definition]} | {tag: 'Equal'; contents: [Expr, Expr]} | {tag: 'Holds'; contents: Expr} | {tag: 'Implies'; contents: [Expr, Definition]} | {tag: 'And'; contents: [Definition, Definition]} | {tag: 'Invoke'; contents: [string, Expr[]]};
|
|
10
10
|
export interface Expectation { actual: Expr; expected: number | string | boolean }
|
|
11
11
|
export interface Example { exampleName: string; bindings: [string, number | string | boolean][]; expectations: Expectation[] }
|
|
12
12
|
export interface Law { lawName: string; parameters: [string, Type][]; requirements: Type[]; definition: Definition; description: string; rationale: string; examples: Example[]; references: string[]; location: Location }
|
|
13
|
-
export
|
|
13
|
+
export type Assertion = {tag: 'AssertEqual'; contents: [Expr, Expr]} | {tag: 'AssertImplies'; contents: [Expr, Assertion]} | {tag: 'AssertAll'; contents: Assertion[]};
|
|
14
|
+
export interface Expanded { owner: string; name: string; inputs: {inputName: string; inputId: string; inputType: Type}[]; left: Expr; right: Expr; guards: Expr[]; assertion: Assertion; trace: string[]; original: Law }
|
|
14
15
|
export interface CheckRequest { sources: Source[] }
|
|
15
16
|
export interface GenerationRequest extends CheckRequest { target: Target; sourceDir?: string; testDir?: string }
|
|
16
17
|
export interface Result { diagnostics: Diagnostic[]; laws?: Expanded[]; expansions?: string[]; files?: Artifact[] }
|