lawspec 0.5.0 → 0.7.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/REFINEMENTS.md ADDED
@@ -0,0 +1,158 @@
1
+ # Refinements and abstract integers (0.7.0)
2
+
3
+ A refinement restricts a scalar domain with a pure Boolean expression. LawSpec
4
+ checks concrete examples, generates satisfying input tuples, and checks adapter
5
+ contracts during testing. It does not prove that an adapter is correct.
6
+
7
+ ## Exact integers without a storage width
8
+
9
+ `Integer` is the mathematical integer domain. Integer literals default to it;
10
+ integer addition, subtraction, multiplication, negation, quotient, and remainder
11
+ produce it. Calculations use arbitrary precision internally. `BigInt` remains
12
+ available when a concrete arbitrary-precision adapter representation is wanted.
13
+
14
+ ```lawspec
15
+ unit example.increment
16
+ successor :: (x :: Int8) -> (result :: Integer where result == x + 1)
17
+ ```
18
+
19
+ This signature generates contract tests without a separate law. The maximum
20
+ input, `127`, requires the value `128`. Returning a wrapped `-128` fails.
21
+ `Integer` does not assert which storage width the implementation used.
22
+
23
+ Abstract integer results accept Python `int` (excluding `bool`), JavaScript or
24
+ TypeScript `bigint` and safe integral `number` values, Java/Kotlin standard signed
25
+ integral wrappers and `BigInteger` through `Number`, and Go signed/unsigned integer
26
+ values or `big.Int` values/pointers through `any`. Floating representations are
27
+ rejected even when their current value is integral. Haskell uses `IntegerValue`:
28
+
29
+ ```haskell
30
+ successor :: Int8 -> IntegerValue
31
+ successor x = integerValue (toInteger x + 1)
32
+ ```
33
+
34
+ `integerValue :: Integral a => a -> IntegerValue` erases the width losslessly.
35
+ Abstract integer arguments use the target's arbitrary-precision integer type.
36
+ Concrete signatures keep their existing checked native mappings.
37
+
38
+ ## Inline and named refinements
39
+
40
+ Use `where` on a quantified input, function argument, or function result:
41
+
42
+ ```lawspec
43
+ `for all` (x :: Int8) (y :: Int8 where y > Int8.max - x) .
44
+ add x y = x + y
45
+ ```
46
+
47
+ A predicate can reference its own value and earlier inputs. A function result
48
+ predicate can also reference the function's named arguments. Forward value
49
+ references are errors. `Int8.min` and `Int8.max` denote representation bounds;
50
+ machine-sized bounds use the requested `machineBits` profile.
51
+
52
+ Named refinements distinguish type parameters from value parameters:
53
+
54
+ ```lawspec
55
+ refinement Between
56
+ (T :: Type) (minimum :: T) (maximum :: T)
57
+ requires Ordered T is
58
+ (value :: T where minimum <= value && value <= maximum)
59
+ end
60
+
61
+ refinement AdditionOverflows
62
+ (T :: Type) (left :: T)
63
+ requires Integer T Bounded T is
64
+ (right :: T where left + right > T.max)
65
+ end
66
+
67
+ add :: (x :: Int8)
68
+ -> (y :: AdditionOverflows Int8 x)
69
+ -> (result :: Integer where result == x + y)
70
+ ```
71
+
72
+ `(x :: Between Int8 1 10)` applies a refinement. Parenthesize compound arguments,
73
+ for example `Between Int8 (-10) (5 + 5)`. Later value-parameter types can use
74
+ earlier parameters. Value arguments must satisfy their declared domains,
75
+ including refinements. Substitution avoids capturing names from the call site.
76
+ Declarations can appear in any order within a unit; recursive aliases are errors.
77
+ Fixed refinements are the zero-parameter form:
78
+
79
+ ```lawspec
80
+ refinement PositiveInt8 is (value :: Int8 where value > 0) end
81
+ ```
82
+
83
+ Aliases preserve their underlying native representation. They can be nested in
84
+ `Nullable` and `Optional`; inner predicates are checked only for present values.
85
+ Type parameters range over the supported scalar types, including presence types.
86
+
87
+ `requires Integer T` describes integer capabilities in a generic declaration.
88
+ `Integer` entails `Eq` and `Ordered`. `Ordered` currently covers exact real numbers
89
+ and floats, with the existing IEEE comparisons (including NaN behavior).
90
+ `Bounded` covers fixed and machine-sized integers. Bounds refer to the underlying
91
+ integer representation, rather than a tighter interval inferred from a predicate.
92
+ Capabilities are checked on generic declarations and their specializations.
93
+
94
+ ## Predicate expressions and contracts
95
+
96
+ Predicates support existing pure arithmetic, comparisons, scalar constructors,
97
+ conversions, and built-in helpers. `&&`, `||`, and `!` short-circuit; comparisons
98
+ bind more tightly than `&&`, which binds more tightly than `||`. Assertion `and`
99
+ continues to combine law conclusions. Exact/inexact mixing still needs an explicit
100
+ conversion, and floating expressions retain their declared precision.
101
+
102
+ `prelude.length` counts Unicode scalars for Text, code points for CodePointText,
103
+ UTF-16 units for Utf16Text, and octets for Bytes. `prelude.isPresent` and
104
+ `prelude.presentValue` inspect tagged presence values; guard extraction with
105
+ `isPresent` before accessing a possibly absent value.
106
+
107
+ Adapter calls are forbidden in refinements. Predicate errors such as division by
108
+ zero are reported as failures when evaluated; they are not ordinary rejection of
109
+ an input. Short-circuited branches are not evaluated by generation optimizations.
110
+
111
+ Every refined function signature creates a standalone property. Calls from other
112
+ laws also check argument preconditions, invoke the adapter once, snapshot its
113
+ result, and check postconditions. Calling a function outside its precondition is
114
+ a test failure, not a discarded example. Ordinary `implies` guards retain their
115
+ existing conditional behavior.
116
+
117
+ ## Dependent generation and shrinking
118
+
119
+ A quantified law ranges over satisfying tuples. For `AdditionOverflows Int8 x`,
120
+ `x = 0` has no admissible `y`. The generator backtracks to choose another `x`;
121
+ it does not count that dead end as a passing test. Valid pairs satisfy:
122
+
123
+ ```text
124
+ 1 <= x <= 127
125
+ 128 - x <= y <= 127
126
+ ```
127
+
128
+ The generator derives integer bounds from affine comparisons and conjunctions,
129
+ seeds direct comparison values and boundaries, and checks the complete predicate.
130
+ Predicates outside that analysis use bounded sampling. Small finite base-domain
131
+ products are enumerated exhaustively, retaining only satisfying tuples.
132
+
133
+ Shrinking checks refinements again and repairs dependent later inputs when an
134
+ earlier value changes. An overflowing counterexample can shrink to `(1, 127)`;
135
+ it cannot shrink to `(0, 127)` because that pair is outside the domain.
136
+
137
+ Compiler requests and `lawspec.json` accept:
138
+
139
+ ```json
140
+ {
141
+ "generation": {
142
+ "cases": 100,
143
+ "maxAttempts": 10000,
144
+ "maxShrinks": 1000,
145
+ "exhaustiveLimit": 4096
146
+ }
147
+ }
148
+ ```
149
+
150
+ All limits are positive integers. These are the defaults for refinement properties.
151
+ `cases` counts accepted property inputs; explicit examples and boundary fixtures
152
+ are additional checks. Exhausted searches report an error with reproduction
153
+ information. Exhaustion does not prove that the mathematical domain is empty.
154
+ Statically established empty executable domains and invalid examples are rejected.
155
+
156
+ See [the bundled examples](examples/specs/refinements.lawspec) for overflow,
157
+ abstract integer arguments/results, dependent bounds, optional refinements,
158
+ floating classification, and raw-byte lengths.
package/bin/lawspec.mjs CHANGED
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { createCompiler } from "../api.mjs";
5
5
  import { targets, templates, commands, setup } from "../templates.mjs";
6
6
  import { generateExamples } from "../examples-command.mjs";
7
+ import { showScalar } from "../scalars.mjs";
7
8
  import { doctor } from "../doctor.mjs";
8
9
  import {
9
10
  readOptional,
@@ -18,7 +19,7 @@ const options = {};
18
19
  const positional = [];
19
20
  for (let i = 0; i < args.length; i++) {
20
21
  const arg = args[i];
21
- if (["--target", "--project", "--config", "--output"].includes(arg)) {
22
+ if (["--target", "--project", "--config", "--output", "--machine-bits"].includes(arg)) {
22
23
  if (!args[i + 1] || args[i + 1].startsWith("--"))
23
24
  throw new Error(`Missing value for ${arg}`);
24
25
  options[arg.slice(2)] = args[++i];
@@ -138,6 +139,7 @@ async function init() {
138
139
  await readFile(new URL("../starter.lawspec", import.meta.url), "utf8"),
139
140
  true,
140
141
  );
142
+ if (options.machineBits !== undefined) config.machineBits = options.machineBits;
141
143
  config.targets.push({
142
144
  language,
143
145
  root: path.relative(configRoot, root) || ".",
@@ -151,9 +153,19 @@ async function init() {
151
153
  `Configured ${language}. ${hasBuild ? "Existing build files preserved." : "Created missing project build files."}\n${setup[language]}\nNext: lawspec doctor, then lawspec generate.`,
152
154
  );
153
155
  }
156
+ function showType(type) {
157
+ if (type.tag === 'Named' || type.tag === 'Variable') return type.contents;
158
+ if (type.tag === 'Applied') return `${type.contents[0]} (${showType(type.contents[1])})`;
159
+ return `${showType(type.contents[0])} -> ${showType(type.contents[1])}`;
160
+ }
154
161
  function showExpression(expr) {
155
162
  const value = expr.contents;
156
- if (expr.tag === "Var") return value;
163
+ if (expr.tag === "Var" || expr.tag === "Number") return value;
164
+ if (expr.tag === "DecimalNumber") return `${value[0]}e${value[1]}`;
165
+ if (expr.tag === "ScalarLit") return showScalar(value);
166
+ if (expr.tag === "Binary") return `(${showExpression(value[1])} ${value[0]} ${showExpression(value[2])})`;
167
+ if (expr.tag === "Unary") return `${value[0]}(${showExpression(value[1])})`;
168
+ if (expr.tag === "Annotate") return `(${showExpression(value[0])} :: ${showType(value[1])})`;
157
169
  if (
158
170
  expr.tag === "Number" ||
159
171
  expr.tag === "StringLit" ||
@@ -170,28 +182,32 @@ function explainExamples(law) {
170
182
  (ex) =>
171
183
  `\nexample ${JSON.stringify(ex.exampleName)}\n` +
172
184
  ex.bindings
173
- .map(([n, v]) => ` ${n} = ${JSON.stringify(v)}`)
185
+ .map(([n, v]) => ` ${n} = ${showScalar(v)}`)
174
186
  .join("\n") +
175
187
  "\n" +
176
188
  ex.expectations
177
189
  .map(
178
190
  (e) =>
179
- ` expect ${showExpression(e.actual)} = ${JSON.stringify(e.expected)}`,
191
+ ` expect ${showExpression(e.actual)} = ${showScalar(e.expected)}`,
180
192
  )
181
193
  .join("\n"),
182
194
  )
183
195
  .join("\n");
184
196
  }
185
197
  async function main() {
198
+ if (options['machine-bits'] !== undefined) {
199
+ options.machineBits = Number(options['machine-bits']);
200
+ if (![32,64].includes(options.machineBits)) throw new Error('machineBits must be 32 or 64');
201
+ }
186
202
  if (!verb || ["help", "--help", "-h"].includes(verb)) {
187
203
  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: " +
204
+ "LawSpec 0.7.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>, --machine-bits <32|64>, --json\nGeneration: --dry-run, --check\nTargets: " +
189
205
  targets.join(", "),
190
206
  );
191
207
  return;
192
208
  }
193
209
  if (verb === "--version") {
194
- output("0.5.0");
210
+ output("0.7.0");
195
211
  return;
196
212
  }
197
213
  if (positional.length > (verb === "explain" ? 1 : 0))
@@ -256,7 +272,8 @@ async function main() {
256
272
  if (reports.some((r) => !r.ok)) process.exitCode = 1;
257
273
  return;
258
274
  }
259
- const input = { sources: await sources(config) };
275
+ if (config.machineBits !== undefined && ![32, 64].includes(config.machineBits)) throw new Error("machineBits must be 32 or 64");
276
+ const input = { sources: await sources(config), generation: config.generation, machineBits: options.machineBits ?? config.machineBits ?? 64 };
260
277
  const compiler = await createCompiler();
261
278
  if (verb === "check") {
262
279
  const result = diagnostics(await compiler.check(input));
package/build.json CHANGED
@@ -2,38 +2,64 @@
2
2
  "version": 1,
3
3
  "compilerSources": [
4
4
  "package.yaml",
5
+ "runtime/LawSpecRuntime.hs",
6
+ "runtime/LawSpecRuntime.java",
7
+ "runtime/lawspec_runtime.go",
8
+ "runtime/lawspec_runtime.mjs",
9
+ "runtime/lawspec_runtime.py",
5
10
  "src/LawSpec/Api.hs",
6
11
  "src/LawSpec/Compile.hs",
12
+ "src/LawSpec/Domain.hs",
7
13
  "src/LawSpec/Emit.hs",
14
+ "src/LawSpec/Eval.hs",
8
15
  "src/LawSpec/Gen.hs",
9
16
  "src/LawSpec/Model.hs",
17
+ "src/LawSpec/NativeScalarEmit.hs",
10
18
  "src/LawSpec/Parser.hs",
11
19
  "src/LawSpec/Prelude.hs",
20
+ "src/LawSpec/Refinement.hs",
21
+ "src/LawSpec/RuntimeSources.hs",
22
+ "src/LawSpec/Scalar.hs",
23
+ "src/LawSpec/ScalarEmit.hs",
12
24
  "stack.yaml",
13
25
  "stack.yaml.lock",
26
+ "tools/embed-runtimes.py",
14
27
  "wasm/app/Exports.hs",
15
28
  "wasm/cabal.project",
16
29
  "wasm/cabal.project.freeze",
17
30
  "wasm/lawspec-wasm.cabal"
18
31
  ],
19
32
  "digests": {
20
- "package.yaml": "deae9eb35385fd0e08aa44432ae718e7ba6bad8e8320c728018ee9d224733c8c",
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",
33
+ "package.yaml": "ca15186d8aa807c58258a4b0fef8fa7d64afaa828f2e71803b4e8741066422fe",
34
+ "runtime/LawSpecRuntime.hs": "1c3ca22232c64227717d836a57d375d906f748ecde6b79705944a2319ce5064d",
35
+ "runtime/LawSpecRuntime.java": "5637914bd491f75ad0c75fb19d1bec798164295b7bcf9d23e1549e8ebf780d01",
36
+ "runtime/lawspec_runtime.go": "62d2041c33760b67db619e0a803501768a505c4dfd9837bca85e158fff81fda9",
37
+ "runtime/lawspec_runtime.mjs": "7d0abd53fa45dd9828c10ff2ffa7876162e965e7bb4913f4c460af13e06142fa",
38
+ "runtime/lawspec_runtime.py": "7541353effebd133e50cd00a3f4d8999651c93ba751a6d951164b1f390ce0c15",
39
+ "src/LawSpec/Api.hs": "f1a283fd3696e8e0dd1bd6240ba636a4c1abe19c6e0ea25d6d80a1af89f762cb",
40
+ "src/LawSpec/Compile.hs": "a67ee7cb19ebeea644bbf8dbdc0647323a77197b15f8e87fe2227deb6c995b8e",
41
+ "src/LawSpec/Domain.hs": "8ad50ad3a37aacb3a429c2d7924279e2a3b973204ce3c5c04c634c1b9ada0151",
42
+ "src/LawSpec/Emit.hs": "0b29d449f762af635bb65e5fcbf1f4cfffb1b91b28db115b08b2309557498693",
43
+ "src/LawSpec/Eval.hs": "9bdb694352ec4bd7140cdf8ab18045978b7407e93db5b661270160d08e4e9b7d",
44
+ "src/LawSpec/Gen.hs": "d4b4e5a21f5a2886dcdc98f56f649980d961c0570d9463154bed459844ab66aa",
45
+ "src/LawSpec/Model.hs": "67cad75f1d344bc2dfc997731c37f68994c6a6b568e03209e5b446484ae54674",
46
+ "src/LawSpec/NativeScalarEmit.hs": "67f50a7ea381ddada8ecbfc7534917c53f4074f8ce1d4fb6948adb8b7dfb2007",
47
+ "src/LawSpec/Parser.hs": "b6e5ee4a8c58b0ab2709420460f1fcabe287714f6defc0fab6cf52ccd0c416a5",
48
+ "src/LawSpec/Prelude.hs": "b1e155c12f58e7c346551a0c65c5d5af76d045b3f1625977ca2a5d1c97cb8c46",
49
+ "src/LawSpec/Refinement.hs": "060f7bdaaa9cebc17969fa28474e9402cc918ceea0f18cac54050d25010b473c",
50
+ "src/LawSpec/RuntimeSources.hs": "4a49d98919971bdbe2b9eabda4eb2e7cda643aaa65ef1a4e6892b6da40ee8acd",
51
+ "src/LawSpec/Scalar.hs": "d89d8233abd893f65bb20d4dabe11066340eb51b982ad6fa7bce04d3e22c3547",
52
+ "src/LawSpec/ScalarEmit.hs": "e1f05d255db08c8634b6af98fd5d0b938f9a3735f8adf13a952f092a90ee884c",
28
53
  "stack.yaml": "20ccf4d599e355e60b7aa4f814a7cd4299fe2048616cc2e6dbdc22a7bd8cec73",
29
54
  "stack.yaml.lock": "ae222b9c81af920c56e50fa4596fa57786e7fa5a7b461390362b2b2ff63818c7",
55
+ "tools/embed-runtimes.py": "7520956a91f4b4ec8a6ab12078195cf1243c989b388a3f9d0b10a0fa60193bc3",
30
56
  "wasm/app/Exports.hs": "4ecbdac8faa2449e6fc61b93c82e6278fa14374154f29b4dacf0eef433f06a47",
31
57
  "wasm/cabal.project": "021e560afdc5eb4cb7169e7119ecb8f92c9ee170245af909c94b612941bff5cc",
32
58
  "wasm/cabal.project.freeze": "733dbed3d2ecccb26e874fd58f136296dad772184deb1658c196b5d54a0814dc",
33
- "wasm/lawspec-wasm.cabal": "4c96fa2ec9c6cb61ca16f705c23cdae8723054bb252d27d8ef58043f8e31b868",
34
- "npm/core.wasm": "581d42431681aa93b64bcd775d403612974f5cb9c7350be4b07d3ed49575c18e",
59
+ "wasm/lawspec-wasm.cabal": "afdb97eb4a08de190f7533b7be79c6ba11fab9ef30792aca71ef5fb448001106",
60
+ "npm/core.wasm": "32e3f553dc8346a0d7aac63ad556eacc0e791803b8957f0804a609b883ed0ea8",
35
61
  "npm/core_jsffi.js": "88d136efe92f7cff5758c8fec8d9b6bbc9707fe37741cebeeb415fe34ce3d72b",
36
62
  "npm/api.mjs": "d6df654600172131ac66a55b86876fc29cde2a76a0a63bbefff3183488a4837b",
37
- "npm/index.d.ts": "9687bedaa439c0888aa54cb94279f4fbda536122f0e55f6ab4ec3ea82ddb4283"
63
+ "npm/index.d.ts": "ec19e731ed33e0d0481345888c73778ae0d3f2db58e4c3310ba684b801e698bc"
38
64
  }
39
65
  }
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