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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **State the law once. Check it everywhere.**
4
4
 
5
- LawSpec 0.5 compiles reusable laws into native property tests, executable examples,
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.5.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.5.0 -- lawspec init --target javascript
29
- npm install --save-dev lawspec@0.5.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.5's current JVM profile certifies
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.5 generates quantified inputs of all three types,
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
- and unary. Text literals are double-quoted, with escapes such as `\"`, `\\`,
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.5.0/examples/specs/parse_port.lawspec)
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.5.0/examples/specs/boolean_flags.lawspec)
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.5.0/examples/specs/equivalent.lawspec) compares decimal
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.5.0/examples/specs/slug.lawspec)
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.5.0/examples/specs/canonical_url.lawspec)
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.5.0/examples/specs/mixed_inputs.lawspec)
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.5.0.tgz`.
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.5.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: " +
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.5.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": "deae9eb35385fd0e08aa44432ae718e7ba6bad8e8320c728018ee9d224733c8c",
20
+ "package.yaml": "25d79d741039fe21e1164d789fdf4f698f135df7e4c96c82560e4f7efe8fbdf9",
21
21
  "src/LawSpec/Api.hs": "00b1e4bc81231ebd387f10652463904084ebf9c3c4486fd2c2217a6d4dbfc157",
22
- "src/LawSpec/Compile.hs": "fd696c774025cb0ec6eb29f71661ab71ac603314865ef068778ae9df1355b6cb",
23
- "src/LawSpec/Emit.hs": "7f53109a18d2450313c9dc9f9ff0fe31d80f533858baf9b2c30e36b8849b416c",
24
- "src/LawSpec/Gen.hs": "c4b5d4f003c52fb31983794187890ef0dad09c86a451d8e1145974605a5ca0e8",
25
- "src/LawSpec/Model.hs": "148f133296be537d4e076c2716219fdafa6d2a44b26c9de4b033f87694870911",
26
- "src/LawSpec/Parser.hs": "12b652dbbf174a7091609385c4564bd47b99e45329cfc1bcdaa3936901b2f966",
27
- "src/LawSpec/Prelude.hs": "98cc20e242d2f70510fa09e59d0f3e387d1ebe438b4d3435ba2f32a1192cfb4e",
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": "4c96fa2ec9c6cb61ca16f705c23cdae8723054bb252d27d8ef58043f8e31b868",
34
- "npm/core.wasm": "581d42431681aa93b64bcd775d403612974f5cb9c7350be4b07d3ed49575c18e",
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": "9687bedaa439c0888aa54cb94279f4fbda536122f0e55f6ab4ec3ea82ddb4283"
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 interface Expanded { owner: string; name: string; inputs: {inputName: string; inputId: string; inputType: Type}[]; left: Expr; right: Expr; guards: Expr[]; trace: string[]; original: Law }
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[] }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lawspec",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "State the law once. Check it everywhere.",
5
5
  "license": "MIT",
6
6
  "type": "module",