-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathReversible.hs
More file actions
300 lines (262 loc) · 6.89 KB
/
Reversible.hs
File metadata and controls
300 lines (262 loc) · 6.89 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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}
{-# LANGUAGE GADTs, KindSignatures, TypeOperators, FlexibleContexts, RankNTypes, ScopedTypeVariables #-}
{-|
Module : Reversible
Copyright : (c) Samuel A. Yallop, 2018
Maintainer : syallop@gmail.com
Stability : experimental
A description of reversible functions.
One use case is for dual parser and printer functions which should be each
others inverse. Pairing these functions into a 'reversible' abstraction can
prevent round-trip properties from being accidentally violated as definitions
are changed.
-}
module Reversible
( ReversibleInstr (..)
, rpure
, rempty
, ralt
, rmap
, rap
, (\|/)
, (\*/)
, (\$/)
, (*/)
, (\*)
, between
, betweenMany
, betweenMany1
, alternatives
, rmany
, rmany1
, allowed
, required
, preferred
, Reversible (..)
, reversible
)
where
import Prelude hiding ((.),id)
import Control.Category
import Reversible.Iso
import Data.Kind
import DSL.Instruction
-- | A 'ReversibleInstr' is a computation with some result type 'a' supporting:
-- - Injecting of pure values via 'RPure'.
-- - Applying reversible 'Iso' functions via 'RMap'.
-- - Alternatives and failure via 'REmpty' and 'RAlt'.
-- - Sequential application via 'RAp'.
-- - You may inject your own instruction type 'i'.
data ReversibleInstr (i :: (Type -> Type) -> Type -> Type) (p :: Type -> Type) (a :: Type) where
-- | RPure is similar to 'Applicative' 'pure' - it reversibles a value typed 'a'
-- into the 'Reversible' context.
-- Unlike Applicative, we must be able to compare the value for equality to
-- permit reversal.
RPure
:: Eq a
=> a
-> ReversibleInstr i p a
-- | REmpty is similar to 'Alternative's 'empty' - it encodes a notion of
-- failure.
REmpty
:: ReversibleInstr i p a
-- | RAlt is similar to 'Alternative's '<|>' - it encodes a notion of choice.
RAlt
:: p a
-> p a
-> ReversibleInstr i p a
-- | RMap is similar to 'Functor's 'fmap' - it allows mapping a function over
-- something in the 'Reversible' context.
-- Unlike fmap which takes a plain function, RMap accepts an 'Iso' to permit
-- reversal.
RMap
:: Show a
=> Iso a b
-> p a
-> ReversibleInstr i p b
-- | RAp is similar to 'Applicative's '<*>' or 'Monad's 'ap' - it encodes a
-- notion of sequential application.
-- Unlike '<*>', RAp tuples its sequential results instead of using a plain
-- function to permit reversal.
RAp
:: (Show a, Show b)
=> p a
-> p b
-> ReversibleInstr i p (a,b)
ReversibleInstr
:: i p a
-> ReversibleInstr i p a
newtype Reversible i a = Reversible (ReversibleInstr i (Reversible i) a)
-- | Assert an instruction type is reversible.
reversible
:: i :<- i'
=> i (Reversible i') a
-> Reversible i' a
reversible = Reversible . ReversibleInstr . inj
-- | rpure is similar to 'Applicative' 'pure' - it reversibles a value typed 'a'
-- into the 'Reversible' context.
-- Unlike Applicative, we must be able to compare the value for equality to
-- permit reversal.
rpure
:: Eq a
=> a
-> Reversible i a
rpure = Reversible . RPure
-- | rempty is similar to 'Alternative's 'empty' - it encodes a notion of
-- failure.
rempty
:: Reversible i a
rempty = Reversible REmpty
-- | ralt is similar to 'Alternative's '<|>' - it encodes a notion of choice.
ralt
:: Reversible i a
-> Reversible i a
-> Reversible i a
ralt l r = Reversible $ RAlt l r
-- | rmap is similar to 'Functor's 'fmap' - it allows mapping a function over
-- something in the 'Reversible' context.
-- Unlike fmap which takes a plain function, rmap accepts an 'Iso' to permit
-- reversal.
rmap
:: Show a
=> Iso a b
-> Reversible i a
-> Reversible i b
rmap iso p = Reversible $ RMap iso p
-- | rap is similar to 'Applicative's '<*>' or 'Monad's 'ap' - it encodes a
-- notion of sequential application.
-- Unlike '<*>', rap tuples its sequential results instead of using a plain
-- function to permit reversal.
rap
:: ( Show a
, Show b
)
=> Reversible i a
-> Reversible i b
-> Reversible i (a,b)
rap l r = Reversible $ RAp l r
infixl 3 \|/
infixr 6 \*/
infix 5 \$/
-- | Infix 'ralt'.
(\|/)
:: Reversible i a
-> Reversible i a
-> Reversible i a
(\|/) = ralt
-- | Infix 'rap'.
(\*/)
:: ( Show a
, Show b
)
=> Reversible i a
-> Reversible i b
-> Reversible i (a,b)
(\*/) = rap
-- | Infix 'rmap'.
(\$/)
:: ( Show a
, Show b
)
=> Iso a b
-> Reversible i a
-> Reversible i b
(\$/) = rmap
-- | Execute a 'Reversible' with a discardable result, then sequentially execute
-- another.
-- Similar to 'Applicative's *>' except the discarded computation must return
-- '()'.
(*/)
:: Show a
=> Reversible i ()
-> Reversible i a
-> Reversible i a
rl */ rr = inverse unitIso . flipIso \$/ rl \*/ rr
-- | Execute a 'Reversible' with a result to return, then sequentially execute
-- another with a discardable result.
-- Similar to 'Applicative's <*' except the discarded computation must return
-- '()'.
(\*)
:: Show a
=> Reversible i a
-> Reversible i ()
-> Reversible i a
rl \* rr = inverse unitIso \$/ rl \*/ rr
-- | A 'Reversible' function between two others.
between
:: Show a
=> Reversible i ()
-> Reversible i a
-> Reversible i ()
-> Reversible i a
between l a r = l */ a \* r
-- | A 'Reversible' function which may occur between many matching pairs of some
-- other 'Reversible's.
--
-- E.G.
--
-- betweenMany "(" r ")" is either:
-- - r
-- - (r)
-- - ((r))
-- but never:
-- - (r
-- - ((r)
-- - (((r
-- as the between reversibles do not match.
betweenMany
:: Show a
=> Reversible i ()
-> Reversible i a
-> Reversible i ()
-> Reversible i a
betweenMany l a r =
a \|/ (l */ (between l a r) \* r)
-- | 'betweenMany' where the 'reversible' must occur with atleast one matching
-- pair of some other 'Reversibles'.
betweenMany1
:: Show a
=> Reversible i ()
-> Reversible i a
-> Reversible i ()
-> Reversible i a
betweenMany1 l a r = l */ betweenMany l a r \* r
-- | A list of alternative Reversibles.
alternatives
:: [Reversible i a]
-> Reversible i a
alternatives = foldr ralt rempty
-- | Zero or many reversible functions.
rmany
:: ( Eq a
, Show a
)
=> Reversible i a
-> Reversible i [a]
rmany g = rmany1 g \|/ rpure []
-- | One or many reversible functions.
rmany1
:: ( Eq a
, Show a
)
=> Reversible i a
-> Reversible i [a]
rmany1 g = consIso \$/ g \*/ rmany g
-- | A reversible computation is allowed to succeed when ran forwards but
-- ignored backwards.
allowed
:: Reversible i ()
-> Reversible i ()
allowed g = ignoreIso [] \$/ rmany g
-- | A reversible computation is required to succeed when ran forwards and runs
-- backwards.
required
:: Reversible i ()
-> Reversible i ()
required g = g \* allowed g
-- | A reversible computation is allowed to succeed when ran forwards, and runs
-- backwards.
preferred
:: Reversible i ()
-> Reversible i ()
preferred g = ignoreIso [()] \$/ rmany g