-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtest_amb.py
More file actions
127 lines (100 loc) · 5.01 KB
/
test_amb.py
File metadata and controls
127 lines (100 loc) · 5.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# -*- coding: utf-8 -*-
from ..syntax import macros, test, test_raises, the # noqa: F401
from ..test.fixtures import session, testset
from ..amb import (forall, choice, insist, deny, ok, fail,
Assignment, MonadicList, nil)
def runtests():
with testset("MonadicList (internal utility)"):
m = MonadicList(1, 2, 3)
test[tuple(m) == (1, 2, 3)]
test[len(m) == 3]
test[m[0] == 1 and m[1] == 2 and m[2] == 3]
m = MonadicList(nil) # special *item* that produces an empty *list*
test[tuple(m) == ()]
# Monadic bind (for MonadicList, it's flatmap).
# This also tests fmap and join.
m = MonadicList(1, 2, 3)
f = lambda a: MonadicList(a, 10 * a) # a -> M b
test[tuple(m >> f) == (1, 10, 2, 20, 3, 30)]
# .then(...): discard current value, replace by given value.
# The new value must be wrapped in MonadicList.
m = MonadicList(1, 2, 3)
const = MonadicList(42) # M b
test[tuple(m.then(const)) == (42, 42, 42)] # one 42 for each element of m
test_raises[TypeError, m.then(f)] # expected a MonadicList, got a function
m1 = MonadicList(1, 2)
m2 = MonadicList(3, 4, 5)
test[m1 == m1]
test[the[m2] != the[m1]]
m1 = MonadicList(1, 2)
m2 = MonadicList(3, 4)
test[m1 + m2 == MonadicList(1, 2, 3, 4)]
m1 = MonadicList(1, 2)
notamonadiclist = (3, 4)
test_raises[TypeError, m1 + notamonadiclist]
test[MonadicList.from_iterable(range(3)) == MonadicList(0, 1, 2)]
m1 = MonadicList(1, 2, 3)
m2 = m1.copy()
test[the[m2] is not the[m1] and m2 == m1]
double = lambda x: 2 * x
m = MonadicList(1, 2, 3)
test[tuple(m >> MonadicList.lift(double)) == (2, 4, 6)]
m = MonadicList(1, 2, 3)
test_raises[TypeError, m.join()] # join() flattens a nested list, which m isn't
# Usage example for `guard`
m = MonadicList(1, 2, 3)
test[tuple(m >> (lambda x: MonadicList.guard(x % 2 == 1)
.then(MonadicList(x)))) == (1, 3)]
with testset("basic usage"):
test[forall(choice(x=range(5)),
lambda e: e.x) == tuple(range(5))]
# a single choice is silly but allowed
test[forall(choice(x=42),
lambda e: e.x) == (42,)]
# Because this is based on the List monad, a line that returns N items
# causes the lines below it to be evaluated N times (once for each value).
#
# Note this happens even if the output is not assigned to a variable!
#
test[forall(range(2), # do the rest twice
choice(x=range(1, 4)),
lambda e: e.x) == (1, 2, 3, 1, 2, 3)]
# simple filtering
test[forall(choice(y=range(3)),
choice(x=range(3)),
lambda e: (fail if e.x % 2 == 0 else ok),
lambda e: (e.x, e.y)) == ((1, 0), (1, 1), (1, 2))]
# same as:
test[forall(choice(y=range(3)),
choice(x=range(3)),
lambda e: deny(e.x % 2 == 0), # <-- here
lambda e: (e.x, e.y)) == ((1, 0), (1, 1), (1, 2))]
# the opposite of:
test[forall(choice(y=range(3)),
choice(x=range(3)),
lambda e: insist(e.x % 2 == 0), # <-- here
lambda e: (e.x, e.y)) == ((0, 0), (2, 0), (0, 1), (2, 1), (0, 2), (2, 2))]
# capture the "ok" flag
test[forall(choice(y=range(3)),
choice(x=range(3)),
choice(z=lambda e: (fail if e.x % 2 == 0 else ok)),
lambda e: (e.x, e.y, e.z)) == ((1, 0, 'ok'), (1, 1, 'ok'), (1, 2, 'ok'))]
# pythagorean triples
pt = lambda: forall(choice(z=range(1, 21)), # hypotenuse
choice(x=lambda e: range(1, e.z + 1)), # shorter leg
choice(y=lambda e: range(e.x, e.z + 1)), # longer leg
lambda e: insist(e.x * e.x + e.y * e.y == e.z * e.z),
lambda e: (e.x, e.y, e.z))
test[tuple(sorted(pt())) == ((3, 4, 5), (5, 12, 13), (6, 8, 10),
(8, 15, 17), (9, 12, 15), (12, 16, 20))]
with testset("error cases"):
test_raises[ValueError, choice(a=1, b=2)] # choice() takes only one binding
# To trigger this corner case, we must manually create an `Assignment`
# that has an invalid name - in normal use, `choice()` protects against
# that by its syntax, since the name of a kwarg must be a valid identifier.
invalid_name = "∀δ>0∃ε>0:f(x+δ)-f(x)<ε"
test_raises[ValueError, forall(Assignment(invalid_name, 42))]
test_raises[TypeError, forall(lambda: 42)] # callable body must be able to take in the environment
if __name__ == '__main__': # pragma: no cover
with session(__file__):
runtests()