-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtest_monadic_do.py
More file actions
143 lines (122 loc) · 4.6 KB
/
test_monadic_do.py
File metadata and controls
143 lines (122 loc) · 4.6 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# -*- coding: utf-8 -*-
"""Tests for the `with monadic_do[M] as result:` macro."""
from ...syntax import macros, test, test_raises, the, monadic_do # noqa: F401
from ...test.fixtures import session, testset
from ...llist import nil
from ...monads import Maybe, Either, Left, Right, List, Writer, State, Reader
def runtests():
with testset("basic expansion (Maybe)"):
with monadic_do[Maybe] as a:
[x := Maybe(10),
y := Maybe(x + 1),
Maybe(x + y)]
test[a == Maybe(21)]
# Short-circuit: Nothing propagates; later bindings never fire.
with monadic_do[Maybe] as b:
[x := Maybe(nil),
y := Maybe(x + 1),
Maybe(x + y)]
test[b == Maybe(nil)]
# Single-element list: no binds, just the final expression.
with monadic_do[Maybe] as c:
[Maybe(42)]
test[c == Maybe(42)]
with testset("binding-syntax variants"):
# := is the primary binding syntax
with monadic_do[Maybe] as a:
[x := Maybe(3),
Maybe(x * 2)]
test[a == Maybe(6)]
# << is the legacy (discordian-deprecated) alternative
with monadic_do[Maybe] as b:
[x << Maybe(3),
Maybe(x * 2)]
test[b == Maybe(6)]
# Mixed (letdoutil allows both in the same block)
with monadic_do[Maybe] as c:
[x := Maybe(2),
y << Maybe(x + 3),
Maybe(x * y)]
test[c == Maybe(10)]
with testset("sequencing — bare expressions in bindings"):
# Bare expression on a binding line = Haskell's `do { mx; ... }`
# (sequence, not bind). The macro wraps it synthetically as `_ := mexpr`.
with monadic_do[List] as filtered:
[x := List.from_iterable(range(1, 6)),
List.guard(x % 2 == 0),
List(x)]
test[filtered == List(2, 4)]
# Mixed bare + binding lines
with monadic_do[List] as mixed:
[x := List(1, 2, 3),
List.guard(x > 1),
y := List(x * 10),
List((x, y))]
test[mixed == List((2, 20), (3, 30))]
with testset("Either short-circuit"):
with monadic_do[Either] as a:
[x := Right(10),
y := Right(x * 2),
Right(x + y)]
test[a == Right(30)]
# Left short-circuits; second binding not evaluated
called = []
def track(v):
called.append(v)
return Right(v * 2)
with monadic_do[Either] as b:
[x := Left("boom"),
y := track(x),
Right(x + y)]
test[b == Left("boom")]
test[called == []] # `track` never invoked
with testset("List monad — Pythagorean triples"):
def r(lo, hi):
return List.from_iterable(range(lo, hi))
with monadic_do[List] as pt:
[z := r(1, 21),
x := r(1, z + 1),
y := r(x, z + 1),
List.guard(x * x + y * y == z * z),
List((x, y, 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("Writer"):
with monadic_do[Writer] as w:
[x := Writer(10, "got 10; "),
y := Writer(x + 1, "added 1; "),
Writer(y * 2, "doubled; ")]
value, log = w.data
test[value == 22]
test[log == "got 10; added 1; doubled; "]
with testset("State"):
bump = State(lambda s: (s, s + 1))
with monadic_do[State] as st:
[a := bump,
b := bump,
c := bump,
State.unit((a, b, c))]
vals, final = st.run(10)
test[vals == (10, 11, 12)]
test[final == 13]
with testset("Reader"):
with monadic_do[Reader] as rd:
[m := Reader.asks(lambda env: env["multiplier"]),
o := Reader.asks(lambda env: env["offset"]),
Reader.unit(m * 5 + o)]
test[rd.run({"multiplier": 3, "offset": 10}) == 25]
with testset("nested do-blocks"):
# Nested do works because the outer's body is a single statement
# but inside the final expression we can invoke another do.
def maybe_addone():
with monadic_do[Maybe] as inner:
[x := Maybe(10),
Maybe(x + 1)]
return inner
with monadic_do[Maybe] as outer:
[y := maybe_addone(),
Maybe(y * 2)]
test[outer == Maybe(22)]
if __name__ == '__main__': # pragma: no cover
with session(__file__):
runtests()