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/API-MIGRATION.md +85 -0
- package/PRIMITIVES.md +150 -0
- package/README.md +174 -21
- package/REFINEMENTS.md +158 -0
- package/bin/lawspec.mjs +24 -7
- package/build.json +37 -11
- package/core.wasm +0 -0
- package/examples/specs/algebra.lawspec +254 -0
- package/examples/specs/currying.lawspec +60 -0
- package/examples/specs/refinements.lawspec +75 -0
- package/examples/specs/scalar_adapters.lawspec +83 -0
- package/examples/specs/scalar_catalog.lawspec +166 -0
- package/examples/specs/scalars.lawspec +138 -0
- package/examples-command.mjs +1 -1
- package/index.d.ts +21 -10
- package/package.json +2 -2
- package/scalars.mjs +13 -0
- package/templates.mjs +1 -1
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} = ${
|
|
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)} = ${
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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": "
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"src/LawSpec/
|
|
27
|
-
"src/LawSpec/
|
|
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": "
|
|
34
|
-
"npm/core.wasm": "
|
|
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": "
|
|
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
|