lawspec 0.8.0 → 0.10.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 +180 -3
- package/GO.md +144 -0
- package/HASKELL.md +171 -0
- package/JAVA.md +113 -0
- package/KOTLIN.md +214 -0
- package/LANGUAGE.md +240 -12
- package/NATIVE-BINDINGS.md +1046 -0
- package/PRIMITIVES.md +1 -1
- package/PYTHON.md +152 -0
- package/README.md +86 -23
- package/REFINEMENTS.md +289 -3
- package/RELEASE-0.10.md +60 -0
- package/RELEASE-0.9.md +61 -0
- package/RUST.md +115 -1
- package/WEB.md +144 -0
- package/api.mjs +28 -2
- package/bin/lawspec.mjs +23 -7
- package/build.json +199 -37
- package/core.wasm +0 -0
- package/examples/native-payments/go/example/payments/domain.go +38 -0
- package/examples/native-payments/go/example/payments/native_generators_test.go +13 -0
- package/examples/native-payments/go/lawspec.json +136 -0
- package/examples/native-payments/haskell/lawspec.json +149 -0
- package/examples/native-payments/haskell/src/PaymentsDomain.hs +21 -0
- package/examples/native-payments/haskell/test/PaymentGenerators.hs +12 -0
- package/examples/native-payments/java/lawspec.json +165 -0
- package/examples/native-payments/java/src/main/java/domain/PaymentsDomain.java +32 -0
- package/examples/native-payments/java/src/test/java/domain/PaymentGenerators.java +16 -0
- package/examples/native-payments/javascript/lawspec.json +149 -0
- package/examples/native-payments/javascript/src/payments_domain.mjs +44 -0
- package/examples/native-payments/javascript/test/lawspec_generators.mjs +6 -0
- package/examples/native-payments/kotlin/lawspec.json +165 -0
- package/examples/native-payments/kotlin/src/main/kotlin/domain/PaymentsDomain.kt +17 -0
- package/examples/native-payments/kotlin/src/test/kotlin/domain/PaymentGenerators.kt +12 -0
- package/examples/native-payments/python/lawspec.json +149 -0
- package/examples/native-payments/python/src/payments_domain.py +60 -0
- package/examples/native-payments/python/tests/lawspec_generators.py +15 -0
- package/examples/native-payments/rust/lawspec.json +167 -0
- package/examples/native-payments/rust/src/domain.rs +37 -0
- package/examples/native-payments/rust/src/lib.rs +2 -0
- package/examples/native-payments/rust/tests/support/lawspec_generators.rs +11 -0
- package/examples/native-payments/typescript/lawspec.json +149 -0
- package/examples/native-payments/typescript/src/payments_domain.ts +42 -0
- package/examples/native-payments/typescript/test/lawspec_generators.ts +8 -0
- package/examples/specs/collections.lawspec +67 -0
- package/examples/specs/data_types.lawspec +66 -0
- package/examples/specs/finite_data.lawspec +37 -0
- package/examples/specs/list_contracts.lawspec +136 -0
- package/examples/specs/list_refinements.lawspec +51 -0
- package/examples/specs/matching.lawspec +49 -0
- package/examples/specs/payments.lawspec +76 -0
- package/examples/specs/recursive_refinements.lawspec +41 -0
- package/examples/specs/refined_definitions.lawspec +44 -0
- package/examples/specs/sum_refinements.lawspec +49 -0
- package/examples/specs/total_functions.lawspec +101 -0
- package/examples-command.mjs +11 -1
- package/files.mjs +16 -7
- package/index.d.ts +297 -27
- package/native-examples.mjs +81 -0
- package/package.json +2 -2
- package/templates.mjs +107 -17
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"sources": [
|
|
4
|
+
"laws"
|
|
5
|
+
],
|
|
6
|
+
"machineBits": 64,
|
|
7
|
+
"targets": [
|
|
8
|
+
{
|
|
9
|
+
"language": "typescript",
|
|
10
|
+
"root": ".",
|
|
11
|
+
"nativeBindings": {
|
|
12
|
+
"types": [
|
|
13
|
+
{
|
|
14
|
+
"type": "example.payments::type::Currency",
|
|
15
|
+
"native": [
|
|
16
|
+
"payments_domain",
|
|
17
|
+
"CurrencyCode"
|
|
18
|
+
],
|
|
19
|
+
"constructors": [
|
|
20
|
+
{
|
|
21
|
+
"constructor": "USD",
|
|
22
|
+
"native": [
|
|
23
|
+
"payments_domain",
|
|
24
|
+
"Dollars"
|
|
25
|
+
],
|
|
26
|
+
"style": "unit",
|
|
27
|
+
"fields": []
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"constructor": "EUR",
|
|
31
|
+
"native": [
|
|
32
|
+
"payments_domain",
|
|
33
|
+
"Euros"
|
|
34
|
+
],
|
|
35
|
+
"style": "unit",
|
|
36
|
+
"fields": []
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"constructor": "GBP",
|
|
40
|
+
"native": [
|
|
41
|
+
"payments_domain",
|
|
42
|
+
"Pounds"
|
|
43
|
+
],
|
|
44
|
+
"style": "unit",
|
|
45
|
+
"fields": []
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "example.payments::type::Money",
|
|
51
|
+
"native": [
|
|
52
|
+
"payments_domain",
|
|
53
|
+
"Price"
|
|
54
|
+
],
|
|
55
|
+
"constructors": [
|
|
56
|
+
{
|
|
57
|
+
"constructor": "Money",
|
|
58
|
+
"native": [
|
|
59
|
+
"payments_domain",
|
|
60
|
+
"Price"
|
|
61
|
+
],
|
|
62
|
+
"style": "record",
|
|
63
|
+
"fields": [
|
|
64
|
+
{
|
|
65
|
+
"field": "amount",
|
|
66
|
+
"native": "major"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"field": "currency",
|
|
70
|
+
"native": "unit"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"type": "example.payments::type::Payment",
|
|
78
|
+
"native": [
|
|
79
|
+
"payments_domain",
|
|
80
|
+
"PaymentStatus"
|
|
81
|
+
],
|
|
82
|
+
"constructors": [
|
|
83
|
+
{
|
|
84
|
+
"constructor": "Paid",
|
|
85
|
+
"native": [
|
|
86
|
+
"payments_domain",
|
|
87
|
+
"Settled"
|
|
88
|
+
],
|
|
89
|
+
"style": "variant",
|
|
90
|
+
"fields": [
|
|
91
|
+
{
|
|
92
|
+
"field": "value",
|
|
93
|
+
"native": "price"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"constructor": "Declined",
|
|
99
|
+
"native": [
|
|
100
|
+
"payments_domain",
|
|
101
|
+
"Rejected"
|
|
102
|
+
],
|
|
103
|
+
"style": "variant",
|
|
104
|
+
"fields": [
|
|
105
|
+
{
|
|
106
|
+
"field": "reason",
|
|
107
|
+
"native": "explanation"
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"functions": [
|
|
115
|
+
{
|
|
116
|
+
"declaration": "example.payments::addFee",
|
|
117
|
+
"native": [
|
|
118
|
+
"payments_domain",
|
|
119
|
+
"apply_fee"
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"declaration": "example.payments::roundTrip",
|
|
124
|
+
"native": [
|
|
125
|
+
"payments_domain",
|
|
126
|
+
"restore"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"declaration": "example.payments::archive",
|
|
131
|
+
"native": [
|
|
132
|
+
"payments_domain",
|
|
133
|
+
"store"
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
"generators": [
|
|
138
|
+
{
|
|
139
|
+
"type": "example.payments::type::Money",
|
|
140
|
+
"factory": [
|
|
141
|
+
"lawspec_generators",
|
|
142
|
+
"prices"
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Application-owned domain types; no generated domain declarations.
|
|
2
|
+
import * as ls from './lawspec_runtime.js';
|
|
3
|
+
|
|
4
|
+
export class CurrencyCode {}
|
|
5
|
+
export class Dollars extends CurrencyCode {}
|
|
6
|
+
export class Euros extends CurrencyCode {}
|
|
7
|
+
export class Pounds extends CurrencyCode {}
|
|
8
|
+
|
|
9
|
+
export class Price {
|
|
10
|
+
major: ls.Decimal;
|
|
11
|
+
unit: CurrencyCode;
|
|
12
|
+
constructor({unit, major}: {unit: CurrencyCode; major: ls.Decimal}) {
|
|
13
|
+
this.unit = unit;
|
|
14
|
+
this.major = major;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class PaymentStatus {}
|
|
18
|
+
export class Settled extends PaymentStatus {
|
|
19
|
+
price: Price;
|
|
20
|
+
constructor({price}: {price: Price}) {
|
|
21
|
+
super();
|
|
22
|
+
this.price = price;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class Rejected extends PaymentStatus {
|
|
26
|
+
explanation: string;
|
|
27
|
+
constructor({explanation}: {explanation: string}) {
|
|
28
|
+
super();
|
|
29
|
+
this.explanation = explanation;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function apply_fee(price: Price): Price {
|
|
33
|
+
const major = ls.binary('+', price.major, new ls.Decimal(2n, -1n),
|
|
34
|
+
'Decimal', 'Decimal') as ls.Decimal;
|
|
35
|
+
return new Price({major, unit: price.unit});
|
|
36
|
+
}
|
|
37
|
+
export function restore<T>(payment: T): T {
|
|
38
|
+
return payment;
|
|
39
|
+
}
|
|
40
|
+
export function store<T>(payments: T[]): T[] {
|
|
41
|
+
return payments;
|
|
42
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as fc from 'fast-check';
|
|
2
|
+
import * as ls from '../src/lawspec_runtime.js';
|
|
3
|
+
import {Price, Euros} from '../src/payments_domain.js';
|
|
4
|
+
|
|
5
|
+
export function prices(): fc.Arbitrary<Price> {
|
|
6
|
+
return fc.integer({min:100, max:200}).map(cents =>
|
|
7
|
+
new Price({major:new ls.Decimal(BigInt(cents), -2n), unit:new Euros()}));
|
|
8
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
unit example.collections
|
|
2
|
+
|
|
3
|
+
reverse :: List Int32 -> List Int32
|
|
4
|
+
sort :: List Int32 -> List Int32
|
|
5
|
+
sorted :: List Int32 -> Bool
|
|
6
|
+
permutation :: List Int32 -> List Int32 -> Bool
|
|
7
|
+
echoMaybe :: Maybe (Maybe Bool) -> Maybe (Maybe Bool)
|
|
8
|
+
echoEither :: Either (List Int32) (Maybe Bool) -> Either (List Int32) (Maybe Bool)
|
|
9
|
+
echoNested :: List (Maybe (Either CodeUnit16 Bytes)) -> List (Maybe (Either CodeUnit16 Bytes))
|
|
10
|
+
|
|
11
|
+
law `list involution` (f :: List a -> List a) requires Eq a is
|
|
12
|
+
definition is `involution` f end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
law `reverse twice restores the list` is
|
|
16
|
+
definition is `list involution` reverse end
|
|
17
|
+
example `ordered input` is
|
|
18
|
+
x = [3, 1, 2]
|
|
19
|
+
expect reverse x = [2, 1, 3]
|
|
20
|
+
end
|
|
21
|
+
example `empty input` is x = [] expect reverse x = [] end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
law `sorting is idempotent` is
|
|
25
|
+
definition is `idempotent` sort end
|
|
26
|
+
example `duplicates` is x = [3, 1, 3] expect sort x = [1, 3, 3] end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
law `sort produces sorted output` is
|
|
30
|
+
definition is `for all` (xs :: List Int32) . sorted (sort xs) end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
law `sort preserves length` is
|
|
34
|
+
definition is `for all` (xs :: List Int32) . prelude.length (sort xs) = prelude.length xs end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
law `sort preserves elements` is
|
|
38
|
+
definition is `for all` (xs :: List Int32) . permutation (sort xs) xs end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
law `nonempty reversal preserves length` is
|
|
42
|
+
definition is
|
|
43
|
+
`for all` (xs :: List Int32 where prelude.length xs > 0) .
|
|
44
|
+
prelude.length (reverse xs) = prelude.length xs
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
law `Maybe preserves nested absence` is
|
|
49
|
+
definition is `for all` (x :: Maybe (Maybe Bool)) . echoMaybe x = x end
|
|
50
|
+
example `outer absence` is x = Nothing expect echoMaybe x = Nothing end
|
|
51
|
+
example `inner absence` is x = Just Nothing expect echoMaybe x = Just Nothing end
|
|
52
|
+
example `present` is x = Just (Just true) expect echoMaybe x = Just (Just true) end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
law `Either preserves branches` is
|
|
56
|
+
definition is `for all` (x :: Either (List Int32) (Maybe Bool)) . echoEither x = x end
|
|
57
|
+
example `left list` is x = Left [3, 1, 3] expect echoEither x = Left [3, 1, 3] end
|
|
58
|
+
example `right absence` is x = Right Nothing expect echoEither x = Right Nothing end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
law `nested raw values survive` is
|
|
62
|
+
definition is `for all` (xs :: List (Maybe (Either CodeUnit16 Bytes))) . echoNested xs = xs end
|
|
63
|
+
example `surrogate and octets` is
|
|
64
|
+
xs = [Nothing, Just (Left codeUnit16(55296)), Just (Right bytes([0, 255]))]
|
|
65
|
+
expect echoNested xs = [Nothing, Just (Left codeUnit16(55296)), Just (Right bytes([0, 255]))]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
unit example.data_types
|
|
2
|
+
|
|
3
|
+
type Pair (a :: Type) (b :: Type) is
|
|
4
|
+
Pair
|
|
5
|
+
first :: a
|
|
6
|
+
second :: b
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
type Tree (a :: Type) is
|
|
10
|
+
Leaf value :: a
|
|
11
|
+
Branch children :: List (Tree a)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
law `products preserve every field` is
|
|
15
|
+
definition is
|
|
16
|
+
`for all` (pair :: Pair Int8 Bool) .
|
|
17
|
+
(match pair with
|
|
18
|
+
| Pair first second -> Pair first second
|
|
19
|
+
end) = pair
|
|
20
|
+
end
|
|
21
|
+
example `maximum integer and false` is
|
|
22
|
+
pair = Pair 127 false
|
|
23
|
+
expect pair = Pair 127 false
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
law `sums preserve constructor identity and payloads` is
|
|
28
|
+
definition is
|
|
29
|
+
`for all` (tree :: Tree Int8) .
|
|
30
|
+
(match tree with
|
|
31
|
+
| Leaf value -> Leaf value
|
|
32
|
+
| Branch children -> Branch children
|
|
33
|
+
end) = tree
|
|
34
|
+
end
|
|
35
|
+
example `nested branches` is
|
|
36
|
+
tree = Branch [Leaf 127, Branch [], Leaf -128]
|
|
37
|
+
expect tree = Branch [Leaf 127, Branch [], Leaf -128]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
-- A refined type argument constrains the corresponding stored fields. This
|
|
43
|
+
-- predicate becomes a scoped Core traversal, shared by all eight backends.
|
|
44
|
+
refinement Positive is (value :: Int8 where value > 0) end
|
|
45
|
+
|
|
46
|
+
definition positivePair (value :: Positive) :: Pair Positive Bool is
|
|
47
|
+
Pair value true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
definition reciprocalFirst (pair :: Pair Positive Bool) :: Rational is
|
|
51
|
+
match pair with
|
|
52
|
+
| Pair first second -> 1 / first
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
law `named payload contracts justify exact division` is
|
|
57
|
+
definition is
|
|
58
|
+
`for all` (value :: Positive) .
|
|
59
|
+
reciprocalFirst (positivePair value) = 1 / value
|
|
60
|
+
end
|
|
61
|
+
example `one half` is
|
|
62
|
+
value = 2
|
|
63
|
+
expect positivePair value = Pair 2 true
|
|
64
|
+
expect reciprocalFirst (positivePair value) = rational(1, 2)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
unit example.finite_data
|
|
2
|
+
|
|
3
|
+
type Choice is Yes | No end
|
|
4
|
+
|
|
5
|
+
type Pair (a :: Type) (b :: Type) is
|
|
6
|
+
Pair first :: a second :: b
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
law `sum constructors remain distinct` is
|
|
10
|
+
definition is
|
|
11
|
+
`for all` (choice :: Choice) .
|
|
12
|
+
(match choice with
|
|
13
|
+
| Yes -> Yes
|
|
14
|
+
| No -> No
|
|
15
|
+
end) = choice
|
|
16
|
+
end
|
|
17
|
+
example `yes` is choice = Yes expect choice = Yes end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
law `products preserve both fields` is
|
|
21
|
+
definition is
|
|
22
|
+
`for all` (pair :: Pair Bool Bool) .
|
|
23
|
+
(match pair with
|
|
24
|
+
| Pair first second -> Pair first second
|
|
25
|
+
end) = pair
|
|
26
|
+
end
|
|
27
|
+
example `different fields` is
|
|
28
|
+
pair = Pair true false
|
|
29
|
+
expect pair = Pair true false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
law `nested optional products keep presence states` is
|
|
34
|
+
definition is
|
|
35
|
+
`for all` (value :: Maybe (Pair Choice (Nullable Bool))) . value = value
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
unit example.list_contracts
|
|
2
|
+
|
|
3
|
+
definition keep (xs :: List (value :: Int8 where value > 0))
|
|
4
|
+
:: List (value :: Int8 where value > 0)
|
|
5
|
+
is xs end
|
|
6
|
+
|
|
7
|
+
definition stronger (xs :: List (value :: Int8 where value > 10)) :: List Int8 is
|
|
8
|
+
keep (copypositive xs)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
definition positive (value :: a) :: Bool requires Integer a is value > 0 end
|
|
12
|
+
|
|
13
|
+
definition keephelper (xs :: List (value :: a where positive value))
|
|
14
|
+
:: List a requires Integer a
|
|
15
|
+
is xs end
|
|
16
|
+
|
|
17
|
+
definition reuse (xs :: List (value :: Int8 where positive value)) :: List Int8 is
|
|
18
|
+
keephelper xs
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
definition empty (value :: Int8) :: List (element :: Int8 where false) is [] end
|
|
22
|
+
|
|
23
|
+
definition singleton (value :: Int8 where value > 0)
|
|
24
|
+
:: List (element :: Int8 where element > 0)
|
|
25
|
+
is [value] end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
law `stronger domains satisfy the callee` is
|
|
29
|
+
definition is `for all` (xs :: List (value :: Int8 where value > 10)) .
|
|
30
|
+
stronger xs = xs
|
|
31
|
+
end
|
|
32
|
+
example `positive input` is xs = [11, 127] expect stronger xs = [11, 127] end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
law `generic predicates keep their scope` is
|
|
36
|
+
definition is `for all` (xs :: List (value :: Int8 where value > 0)) .
|
|
37
|
+
reuse xs = xs
|
|
38
|
+
end
|
|
39
|
+
example `empty` is xs = [] expect reuse xs = [] end
|
|
40
|
+
example `nonempty` is xs = [1, 2] expect reuse xs = [1, 2] end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
law `empty output satisfies an impossible element domain` is
|
|
44
|
+
definition is `for all` (value :: Int8) . empty value = [] end
|
|
45
|
+
example `zero` is value = 0 expect empty value = [] end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
law `constructed output satisfies its contract` is
|
|
49
|
+
definition is `for all` (value :: Int8 where value > 0) . singleton value = [value] end
|
|
50
|
+
example `maximum` is value = 127 expect singleton value = [127] end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
definition sumreciprocal (xs :: List (value :: Int8 where value != 0)) :: Rational is
|
|
55
|
+
match xs with
|
|
56
|
+
| Nil -> 0
|
|
57
|
+
| Cons first rest -> unwrapnonzero (wrapnonzero (nonzero first)) + sumreciprocal rest
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
definition sumrows (rows :: List (List (value :: Int8 where value != 0))) :: Rational is
|
|
62
|
+
match rows with
|
|
63
|
+
| Nil -> 0
|
|
64
|
+
| Cons first rest -> sumreciprocal first + sumrows rest
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
definition positivetail (xs :: List (value :: Int8 where value > 0))
|
|
69
|
+
:: List (value :: Int8 where value > 0)
|
|
70
|
+
is
|
|
71
|
+
match xs with | Nil -> [] | Cons first rest -> rest end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
definition positivefirst (xs :: List (value :: Int8 where value > 0))
|
|
75
|
+
:: (result :: Int8 where result > 0)
|
|
76
|
+
is
|
|
77
|
+
match xs with | Nil -> 1 | Cons first rest -> first end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
law `reciprocal sum uses each nonzero head` is
|
|
82
|
+
definition is `for all` (value :: Int8 where value != 0) .
|
|
83
|
+
sumreciprocal [value] = 1 / value
|
|
84
|
+
end
|
|
85
|
+
example `half` is value = 2 expect sumreciprocal [value] = rational(1, 2) end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
law `nested membership keeps row contracts` is
|
|
89
|
+
definition is `for all` (value :: Int8 where value != 0) .
|
|
90
|
+
sumrows [[], [value]] = 1 / value
|
|
91
|
+
end
|
|
92
|
+
example `negative half` is value = -2 expect sumrows [[], [value]] = rational(-1, 2) end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
law `tail preserves its positive elements` is
|
|
96
|
+
definition is `for all` (first :: Int8 where first > 0)
|
|
97
|
+
(second :: Int8 where second > 0) . positivetail [first, second] = [second]
|
|
98
|
+
end
|
|
99
|
+
example `two values` is first = 1 second = 2 expect positivetail [first, second] = [2] end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
-- Recursive calls supply their verified result contract by structural induction.
|
|
103
|
+
definition copypositive (xs :: List (value :: Int8 where value > 0))
|
|
104
|
+
:: List (value :: Int8 where value > 0)
|
|
105
|
+
is
|
|
106
|
+
match xs with
|
|
107
|
+
| Nil -> []
|
|
108
|
+
| Cons first rest -> Cons first (copypositive rest)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
definition nonzero (value :: Int8 where value != 0)
|
|
113
|
+
:: (result :: Int8 where result != 0)
|
|
114
|
+
is value end
|
|
115
|
+
|
|
116
|
+
definition wrapnonzero (value :: Int8 where value != 0)
|
|
117
|
+
:: Maybe (result :: Int8 where result != 0)
|
|
118
|
+
is Just value end
|
|
119
|
+
|
|
120
|
+
definition unwrapnonzero (value :: Maybe (element :: Int8 where element != 0))
|
|
121
|
+
:: Rational
|
|
122
|
+
is
|
|
123
|
+
match value with
|
|
124
|
+
| Nothing -> 0
|
|
125
|
+
| Just element -> selectnonzero (Left element)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
definition selectnonzero (value :: Either (element :: Int8 where element != 0) Bool)
|
|
130
|
+
:: Rational
|
|
131
|
+
is
|
|
132
|
+
match value with
|
|
133
|
+
| Left element -> 1 / element
|
|
134
|
+
| Right ignored -> 0
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
unit example.list_refinements
|
|
2
|
+
|
|
3
|
+
definition above (floor :: Int8) (xs :: List Int8) :: Bool is
|
|
4
|
+
match xs with
|
|
5
|
+
| Nil -> true
|
|
6
|
+
| Cons first rest -> first > floor && above floor rest
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
definition positiveRows (rows :: List (List Int8)) :: Bool is
|
|
11
|
+
match rows with
|
|
12
|
+
| Nil -> true
|
|
13
|
+
| Cons first rest -> above 0 first && positiveRows rest
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
law `each element is positive` is
|
|
18
|
+
definition is `for all` (xs :: List (value :: Int8 where value > 0)) .
|
|
19
|
+
above 0 xs = true
|
|
20
|
+
end
|
|
21
|
+
example `empty` is xs = [] expect above 0 xs = true end
|
|
22
|
+
example `positive values` is xs = [1, 127] expect above 0 xs = true end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
law `nested element domains` is
|
|
26
|
+
definition is `for all` (rows :: List (List (value :: Int8 where value > 0))) .
|
|
27
|
+
positiveRows rows = true
|
|
28
|
+
end
|
|
29
|
+
example `empty rows` is rows = [[], [1, 2]] expect positiveRows rows = true end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
law `dependent elements` is
|
|
33
|
+
definition is `for all` (lawspecElement :: Int8)
|
|
34
|
+
(xs :: List (value :: Int8 where value > lawspecElement)) .
|
|
35
|
+
above lawspecElement xs = true
|
|
36
|
+
end
|
|
37
|
+
example `maximum prefix still permits empty` is
|
|
38
|
+
lawspecElement = 127 xs = [] expect above lawspecElement xs = true
|
|
39
|
+
end
|
|
40
|
+
example `nonempty continuation` is
|
|
41
|
+
lawspecElement = 0 xs = [1, 2] expect above lawspecElement xs = true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
law `division stays behind its guard` is
|
|
46
|
+
definition is `for all`
|
|
47
|
+
(xs :: List (value :: Int8 where value != 0 && 1 / value > 0)) .
|
|
48
|
+
above 0 xs = true
|
|
49
|
+
end
|
|
50
|
+
example `positive denominator` is xs = [1] expect above 0 xs = true end
|
|
51
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
unit example.matching
|
|
2
|
+
|
|
3
|
+
-- Rebuilding values checks both constructor identity and every payload field.
|
|
4
|
+
law `list patterns preserve the head and tail` is
|
|
5
|
+
definition is
|
|
6
|
+
`for all` (xs :: List Int8) .
|
|
7
|
+
(match xs with
|
|
8
|
+
| Nil -> []
|
|
9
|
+
| Cons head tail -> Cons head tail
|
|
10
|
+
end) = xs
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
law `nested Maybe patterns preserve each absence state` is
|
|
15
|
+
definition is
|
|
16
|
+
`for all` (value :: Maybe (Maybe Bool)) .
|
|
17
|
+
(match value with
|
|
18
|
+
| Nothing -> Nothing
|
|
19
|
+
| Just inner -> Just (match inner with
|
|
20
|
+
| Nothing -> Nothing
|
|
21
|
+
| Just payload -> Just payload
|
|
22
|
+
end)
|
|
23
|
+
end) = value
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
law `Either patterns preserve their constructor` is
|
|
28
|
+
definition is
|
|
29
|
+
`for all` (value :: Either (List Int8) (Maybe Bool)) .
|
|
30
|
+
(match value with
|
|
31
|
+
| Left items -> Left items
|
|
32
|
+
| Right optional -> Right optional
|
|
33
|
+
end) = value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
-- The annotation gives the empty-case literal the same primitive type as head.
|
|
38
|
+
law `matching can provide a default value` is
|
|
39
|
+
definition is
|
|
40
|
+
`for all` (value :: Maybe Int8) .
|
|
41
|
+
((match value with
|
|
42
|
+
| Nothing -> 0
|
|
43
|
+
| Just payload -> payload
|
|
44
|
+
end) :: Int8) = ((match value with
|
|
45
|
+
| Just payload -> payload
|
|
46
|
+
| Nothing -> 0
|
|
47
|
+
end) :: Int8)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
unit example.payments
|
|
2
|
+
|
|
3
|
+
-- The executable acceptance model for native domain bindings in 0.10.
|
|
4
|
+
-- Applications may call Money "Price", amount "major", and currency "unit";
|
|
5
|
+
-- those representation choices must not alter these laws.
|
|
6
|
+
type Currency is USD | EUR | GBP end
|
|
7
|
+
|
|
8
|
+
type Money is
|
|
9
|
+
Money amount :: Decimal currency :: Currency
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
type Payment is
|
|
13
|
+
Paid value :: Money
|
|
14
|
+
Declined reason :: Text
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
-- These are adapter functions supplied by the application.
|
|
18
|
+
addFee :: Money -> Money
|
|
19
|
+
roundTrip :: Payment -> Payment
|
|
20
|
+
archive :: List (Maybe Payment) -> List (Maybe Payment)
|
|
21
|
+
|
|
22
|
+
-- The model is exact, independent of host decimal rounding settings.
|
|
23
|
+
definition feeModel (money :: Money) :: Money is
|
|
24
|
+
match money with
|
|
25
|
+
| Money amount currency -> Money (amount + 0.2) currency
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
law `fees preserve currency and exact decimal value` is
|
|
30
|
+
definition is
|
|
31
|
+
`for all` (money :: Money) . addFee money = feeModel money
|
|
32
|
+
end
|
|
33
|
+
example `decimal tenths` is
|
|
34
|
+
money = Money 0.1 USD
|
|
35
|
+
expect addFee money = Money 0.3 USD
|
|
36
|
+
end
|
|
37
|
+
example `small amount is not rounded away` is
|
|
38
|
+
money = Money 0.000000000000000000000000000001 EUR
|
|
39
|
+
expect addFee money = Money 0.200000000000000000000000000001 EUR
|
|
40
|
+
end
|
|
41
|
+
example `beyond binary floating precision` is
|
|
42
|
+
money = Money 9007199254740993.1 GBP
|
|
43
|
+
expect addFee money = Money 9007199254740993.3 GBP
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
law `payment variants and payloads survive native conversion` is
|
|
48
|
+
definition is
|
|
49
|
+
`for all` (payment :: Payment) . roundTrip payment = payment
|
|
50
|
+
end
|
|
51
|
+
example `successful payment` is
|
|
52
|
+
payment = Paid (Money 12.34 EUR)
|
|
53
|
+
expect roundTrip payment = Paid (Money 12.34 EUR)
|
|
54
|
+
end
|
|
55
|
+
example `declined payment` is
|
|
56
|
+
payment = Declined "card declined"
|
|
57
|
+
expect roundTrip payment = Declined "card declined"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
law `archives preserve order duplicates and absence` is
|
|
62
|
+
definition is
|
|
63
|
+
`for all` (payments :: List (Maybe Payment)) . archive payments = payments
|
|
64
|
+
end
|
|
65
|
+
example `empty archive` is
|
|
66
|
+
payments = []
|
|
67
|
+
expect archive payments = []
|
|
68
|
+
end
|
|
69
|
+
example `missing is different from a declined payment` is
|
|
70
|
+
payments = [Nothing, Just (Declined ""), Just (Paid (Money 0.1 USD)),
|
|
71
|
+
Just (Paid (Money 0.1 USD))]
|
|
72
|
+
expect archive payments = [Nothing, Just (Declined ""),
|
|
73
|
+
Just (Paid (Money 0.1 USD)),
|
|
74
|
+
Just (Paid (Money 0.1 USD))]
|
|
75
|
+
end
|
|
76
|
+
end
|