-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEnumeration.hs
321 lines (293 loc) · 11.9 KB
/
Enumeration.hs
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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
-- Copyright (c) 2014 Eric McCorkle. All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
--
-- 1. Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- 3. Neither the name of the author nor the names of any contributors
-- may be used to endorse or promote products derived from this software
-- without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS''
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
-- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-- PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS
-- OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-- USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-- OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-- SUCH DAMAGE.
{-# OPTIONS_GHC -Wall -Werror -funbox-strict-fields #-}
{-# LANGUAGE DeriveDataTypeable #-}
-- | Utilities for constructing enumerations over datatypes.
--
-- An 'Enumeration' is a mapping between a particular datatype and a
-- 'Path', which consists of a list of natural numbers. Conceptually,
-- we can think of all the values of a given datatype as being
-- organized into a tree, with nodes representing decisions that
-- narrow down the choices. In such a scheme, a 'Path' represents a
-- path through the tree to a single value, and an 'Enumeration' is a
-- procedure for converting a 'Path' to and from a value.
--
--
-- An 'Enumeration' has two key functions: 'fromPath' and 'toPath',
-- which translate between paths and instances of a datatype. These
-- functions are expected to be inverses; or:
--
-- * @fromPath (toPath v) == v@ for all values in the domain
--
-- Beyond this, there are no additional restrictions. Specifically,
-- two paths /may/ map to the same value.
--
--
-- The 'numBranches' function indicates the maximum value of the first
-- path element for an 'Enumeration'. The minimum value is always 0,
-- and all values between 0 and 'numBranches' must be valid. If there
-- is no upper bound on the value of the first path element,
-- 'numBranches' returns 'Nothing'. The 'toSizedPath' maps a value to
-- a path, which also contains the result of 'numBranches' at each
-- step in the path.
--
--
-- The 'withPrefix' function supplies a partial path to an
-- 'Enumeration', yielding a new 'Enumeration' that maps each value to
-- the same path(s) as the original 'Enumeration', with the prefix
-- added or removed. More formally, if
--
-- * @subenc = withPrefix enc prepath@
--
-- Then
--
-- * @toPath enc val == prepath ++ toPath subenc val@
--
-- * @fromPath enc (prepath ++ subpath) == fromPath subenc subpath@
--
-- With multiple uses of 'withPrefix', the following must be true:
--
-- * @withPrefix (withPrefix enc path1) path2 == withPrefix enc (path1 ++ path2)@
--
-- Finally, if a complete path is given to 'withPrefix', then the
-- result is a singleton encoding that gives the value associated with
-- that path. That is:
--
-- * @fromPath enc fullpath == fromPath (withPrefix enc fullpath) []@
--
-- This provides \"warm-start\" functionality for 'Enumeration's.
-- When translating a large number of 'Path's with the same prefix, it
-- will generally be much more efficient to use 'withPrefix' and use
-- the resulting 'Enumeration' than to translate the 'Path's directly.
--
--
-- The 'prefix' function gives the current prefix for an
-- 'Enumeration'. The following rule describes the relationship
-- between 'prefix' and 'withPrefix':
--
-- * @prefix (withPrefix enc prepath) == prepath@
--
--
-- 'Enumeration's are similar to 'Encoding's from the arith-encode
-- library, except 'Enumeration's are generally more flexible, and can
-- more easily accomodate complex datatypes with invariants. However,
-- as 'Path's are constructed from natural numbers, we can create an
-- 'Enumeration' using a series of 'Encoding's for intermediate data.
-- The functions in this module provide the ability to construct
-- 'Enumeration's using 'Encoding's
--
-- A singleton 'Enumeration' can be constructed using the 'singleton'
-- and 'singletonWithPrefix' functions.
--
-- An 'Encoding' for a datatype can be converted into an 'Enumeration'
-- (where all paths have a single element) using the 'fromEncoding'
-- and 'fromEncodingWithPrefix' functions.
--
-- The 'step' and 'stepWithPrefix' functions construct an
-- 'Enumeration' from an 'Encoding' for an intermediate value, and a
-- generator function that produces an 'Enumeration' from the
-- intermediate value.
--
-- The @withPrefix@ variants for each of these take a prefix path,
-- where the non-@withPrefix@ variants set the prefix to @[]@.
module Data.Enumeration(
-- * Definitions
Enumeration,
Path,
BadPath(..),
IllegalArgument(..),
-- ** Using Enumerations
fromPath,
toPath,
toSizedPath,
withPrefix,
numBranches,
prefix,
-- * Constructions
singleton,
singletonWithPrefix,
fromEncoding,
fromEncodingWithPrefix,
step,
stepWithPrefix
) where
import Control.Exception
import Data.List
import Data.ArithEncode hiding (singleton)
import Data.Typeable
-- | A path that uniquely identifies a value in an @Enumeration@.
type Path = [Integer]
-- | A datatype that represents a mapping between @Path@s and @ty@s.
-- Note that unlike @Encoding@s, not all @Path@s are necessarily
-- valid.
data Enumeration ty =
Enumeration {
-- | Convert a @ty@ to a @Path@
toPath :: !(ty -> Path),
-- | Convert to a list of pairs, where the @fst@ holds
-- the path entry, and @snd@ holds @numBranches@. This is used
-- primarily for encoding values as binary.
toSizedPath :: !(ty -> [(Integer, Maybe Integer)]),
-- | Generate a @ty@ from a @Path@
fromPath :: !(Path -> ty),
-- | Given a prefix path, get an enumeration that generates @ty@s
-- from the rest of the path.
withPrefix :: !(Path -> Enumeration ty),
-- | Get the upper bound on values for the first path component,
-- or @Nothing@ if there is no bound.
numBranches :: !(Maybe Integer),
-- | The prefix path.
prefix :: !Path
}
-- | An exception thrown when a 'Path' is invalid.
data BadPath = BadPath String
deriving Typeable
instance Show BadPath where
show (BadPath "") = "Bad Path"
show (BadPath msg) = "Bad Path: " ++ msg
instance Exception BadPath
showPath :: Path -> String
showPath = intercalate "." . map show
-- | Create an 'Enumeration' with an empty prefix that maps a single
-- value to and from the empty path. Equivalent to
-- @singletonWithPrefix []@
singleton :: Eq ty =>
ty
-- ^ The value to map to and from the empty path.
-> Enumeration ty
singleton = singletonWithPrefix []
-- | Create an 'Enumeration' with a given prefix path that maps a
-- single value to and from the empty path.
singletonWithPrefix :: Eq ty => Path -> ty -> Enumeration ty
singletonWithPrefix prefixPath val =
let
showCompletePath path = showPath (prefixPath ++ path)
fromPathFunc [] = val
fromPathFunc path =
throw $! BadPath $! "Extra path elements " ++ showCompletePath path
toPathFunc val'
| val' == val = []
| otherwise = throw $! IllegalArgument "Bad argument to singleton"
toSizedPathFunc val'
| val' == val = []
| otherwise = throw $! IllegalArgument "Bad argument to singleton"
withPrefixFunc [] = out
withPrefixFunc path =
throw $! BadPath $! "Extra path elements " ++ showCompletePath path
out = Enumeration { fromPath = fromPathFunc, toPath = toPathFunc,
withPrefix = withPrefixFunc, numBranches = Just 0,
prefix = prefixPath, toSizedPath = toSizedPathFunc }
in
out
-- | Create an 'Enumeration' with an empty prefix from a single
-- 'Encoding'. The 'Path' will always be of length 1, and contains
-- the encoded value.
fromEncoding :: Eq ty =>
Encoding ty
-- ^ The 'Encoding' to use
-> Enumeration ty
fromEncoding = fromEncodingWithPrefix []
-- | Create an 'Enumeration' with a given prefix from a single
-- 'Encoding'. The 'Path' will always be of length 1, and contains
-- the encoded value.
fromEncodingWithPrefix :: Eq ty => Path -> Encoding ty -> Enumeration ty
fromEncodingWithPrefix prefixPath enc =
let
fromPathFunc [encoded] = decode enc encoded
fromPathFunc [] = throw $! BadPath "Path too short"
fromPathFunc (_ : path) =
throw $! BadPath $! "Extra path elements " ++ showPath path
toPathFunc val = [encode enc val]
toSizedPathFunc val = [(encode enc val, size enc)]
withPrefixFunc newPrefix @ [encoded] =
singletonWithPrefix (prefixPath ++ newPrefix) (decode enc encoded)
withPrefixFunc [] = out
withPrefixFunc (_ : path) =
throw $! BadPath $! "Extra path elements " ++ showPath path
out = Enumeration { fromPath = fromPathFunc, toPath = toPathFunc,
withPrefix = withPrefixFunc, numBranches = size enc,
prefix = prefixPath, toSizedPath = toSizedPathFunc }
in
out
-- | Create an 'Enumeration' with an empty prefix that uses an
-- 'Encoding' to convert the first element of the path to an interim
-- value, then uses that value to construct an 'Enumeration' to decode
-- the rest of the path.
step :: Encoding ty1
-- ^ The encoding for the first type.
-> (Path -> ty1 -> Enumeration ty2)
-- ^ A function that produces an enumeration from the first type.
-> (ty2 -> ty1)
-- ^ A function that extracts the first type from the second.
-> Enumeration ty2
step = stepWithPrefix []
-- | Create an 'Enumeration' with a prefix that uses an 'Encoding' to
-- convert the first element of the path to an interim value, then
-- uses that value to construct an 'Enumeration' to decode the rest of
-- the path.
stepWithPrefix :: Path
-- ^ The prefix path.
-> Encoding ty1
-- ^ The 'Encoding' for the first type.
-> (Path -> ty1 -> Enumeration ty2)
-- ^ A function that produces an enumeration from the first type.
-> (ty2 -> ty1)
-- ^ A function that extracts the first type from the second.
-> Enumeration ty2
stepWithPrefix prefixPath enc build extract =
let
fromPathFunc (first : rest) =
fromPath (build prefixPath (decode enc first)) rest
fromPathFunc [] = throw $! BadPath "Path too short"
toPathFunc val =
let
extracted = extract val
inner = build prefixPath extracted
in
encode enc extracted : toPath inner val
toSizedPathFunc val =
let
extracted = extract val
inner = build prefixPath extracted
in
(encode enc extracted, size enc) : toSizedPath inner val
withPrefixFunc (first : rest) =
let
extracted = decode enc first
newPrefix = prefixPath ++ [first]
inner = build newPrefix extracted
in
withPrefix inner rest
withPrefixFunc [] = out
out = Enumeration { fromPath = fromPathFunc, toPath = toPathFunc,
withPrefix = withPrefixFunc, numBranches = size enc,
prefix = prefixPath, toSizedPath = toSizedPathFunc }
in
out