File: NumType.lhs

package info (click to toggle)
haskell-numtype 1.2-7
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 84 kB
  • sloc: haskell: 223; makefile: 2
file content (413 lines) | stat: -rw-r--r-- 13,806 bytes parent folder | download | duplicates (4)
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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
Numeric.NumType -- Type-level (low cardinality) integers
Bjorn Buckwalter, bjorn.buckwalter@gmail.com
License: BSD3


= Summary =

This Module provides unary type-level representations, hereafter
referred to as "NumTypes", of the (positive and negative) integers
and basic operations (addition, subtraction, multiplication, division)
on these. While functions are provided for the operations NumTypes
exist solely at the type level and their only value is 'undefined'.

There are similarities with the HNats of the HList library [1],
which was indeed a source of inspiration. Occasionally references
are made to the HNats. The main addition in this module is negative
numbers.

The practical size of the NumTypes is limited by the type checker
stack. If the NumTypes grow too large (which can happen quickly
with multiplication) an error message similar to the following will
be emitted:

    Context reduction stack overflow; size = 20
    Use -fcontext-stack=N to increase stack size to N

This situation could concievably be mitigated significantly by using
e.g. a binary representation of integers rather than Peano numbers.

Also, even if stack size is increased type-checker performance
quickly gets painfully slow. If you will be working with type-level
integers beyond (-20, 20) this module probably isn't for you. They
are, however, eminently suitably for applications such as representing
physical dimensions.


= Preliminaries =

This module requires GHC 6.6 or later. We utilize multi-parameter
type classes, phantom types, functional dependencies and undecidable
instances (and possibly additional unidentified GHC extensions).

> {-# LANGUAGE UndecidableInstances
>            , ScopedTypeVariables
>            , EmptyDataDecls
>            , FunctionalDependencies
>            , MultiParamTypeClasses
>            , FlexibleInstances
>            , DeriveDataTypeable
> #-}

> {- |
>    Copyright  : Copyright (C) 2006-2009 Bjorn Buckwalter
>    License    : BSD3
>
>    Maintainer : bjorn.buckwalter@gmail.com
>    Stability  : Stable
>    Portability: GHC only?
>
> Please refer to the literate Haskell code for documentation of both API
> and implementation.
> -}

> module Numeric.NumType
>   -- Basic classes (exported versions).
>   ( NumType, PosType, NegType, NonZero
>   -- Arithmetic classes.
>   , Succ, Negate, Sum, Div, Mul
>   -- Functions.
>   , toNum, incr, decr, negate, (+), (-), (*), (/)
>   -- Data types.
>   , Zero, Pos, Neg
>   -- Type synonyms for convenience.
>   , Pos1, Pos2, Pos3, Pos4, Pos5, Neg1, Neg2, Neg3, Neg4, Neg5
>   -- Values for convenience.
>   , zero, pos1, pos2, pos3, pos4, pos5, neg1, neg2, neg3, neg4, neg5
>   ) where

> import Prelude hiding ((*), (/), (+), (-), negate)
> import qualified Prelude ((+), (-))
> import Data.Typeable (Typeable)

Use the same fixity for operators as the Prelude.

> infixl 7  *, /
> infixl 6  +, -


= NumTypes =

We start by defining a class encompassing all integers with the
class function 'toNum' that converts from the type-level to a
value-level 'Num'.

> class NumTypeI n where toNum :: (Num a) => n -> a

Then we define classes encompassing all positive and negative
integers respectively. The 'PosTypeI' class corresponds to HList's
'HNat'. We also define a class for non-zero numbers (used to
prohibit division by zero).

> class (NumTypeI n) => PosTypeI n
> class (NumTypeI n) => NegTypeI n
> class (NumTypeI n) => NonZeroI n

Now we use a trick from Oleg Kiselyov and Chung-chieh Shan [2]:

    -- The well-formedness condition, the kind predicate
    class Nat0 a where toInt :: a -> Int
    class Nat0 a => Nat a           -- (positive) naturals

    -- To prevent the user from adding new instances to Nat0 and especially
    -- to Nat (e.g., to prevent the user from adding the instance |Nat B0|)
    -- we do NOT export Nat0 and Nat. Rather, we export the following proxies.
    -- The proxies entail Nat and Nat0 and so can be used to add Nat and Nat0
    -- constraints in the signatures. However, all the constraints below
    -- are expressed in terms of Nat0 and Nat rather than proxies. Thus,
    -- even if the user adds new instances to proxies, it would not matter.
    -- Besides, because the following proxy instances are most general,
    -- one may not add further instances without overlapping instance extension.
    class    Nat0 n => Nat0E n
    instance Nat0 n => Nat0E n
    class    Nat n => NatE n
    instance Nat n => NatE n

We apply this trick to our classes. In our case we will elect to
append an "I" to the internal (non-exported) classes rather than
appending an "E" to the exported classes.

> class    (NumTypeI n) => NumType n
> instance (NumTypeI n) => NumType n
> class    (PosTypeI n) => PosType n
> instance (PosTypeI n) => PosType n
> class    (NegTypeI n) => NegType n
> instance (NegTypeI n) => NegType n
> class    (NonZeroI n) => NonZero n
> instance (NonZeroI n) => NonZero n

We do not have to do this for our other classes. They have the above
classes in their constraints and since the instances are complete
(not proven) a new instance cannot be defined (actually used in the
case of GHC) without overlapping instances.

Now we Define the data types used to represent integers. We begin
with 'Zero', which we allow to be used as both a positive and a
negative number in the sense of the previously defined type classes.
'Zero' corresponds to HList's 'HZero'.

> data Zero deriving Typeable
> instance NumTypeI Zero where toNum _ = 0
> instance PosTypeI Zero
> instance NegTypeI Zero

Next we define the "successor" type, here called 'Pos' (corresponding
to HList's 'HSucc').

> data Pos n deriving Typeable
> instance (PosTypeI n) => NumTypeI (Pos n) where
>   toNum _ = toNum (undefined :: n) Prelude.+ 1
> instance (PosTypeI n) => PosTypeI (Pos n)
> instance (PosTypeI n) => NonZeroI (Pos n)

We could be more restrictive using "data (PosTypeI n) => Pos n" but
this constraint will not be checked (by GHC) anyway when 'Pos' is
used solely at the type level.

Finally we define the "predecessor" type used to represent negative
numbers.

> data Neg n deriving Typeable
> instance (NegTypeI n) => NumTypeI (Neg n) where
>   toNum _ = toNum (undefined :: n) Prelude.- 1
> instance (NegTypeI n) => NegTypeI (Neg n)
> instance (NegTypeI n) => NonZeroI (Neg n)


= Show instances =

For convenience we create show instances for the defined NumTypes.

> instance Show Zero where show _ = "NumType 0"
> instance (PosTypeI n) => Show (Pos n) where show x = "NumType " ++ show (toNum x :: Integer)
> instance (NegTypeI n) => Show (Neg n) where show x = "NumType " ++ show (toNum x :: Integer)


= Negation, incrementing and decrementing =

We start off with some basic building blocks. Negation is a simple
matter of recursively changing 'Pos' to 'Neg' or vice versa while
leaving 'Zero' unchanged.

> class (NumTypeI a, NumTypeI b) => Negate a b | a -> b, b -> a

> instance Negate Zero Zero
> instance (PosTypeI a, NegTypeI b, Negate a b) => Negate (Pos a) (Neg b)
> instance (NegTypeI a, PosTypeI b, Negate a b) => Negate (Neg a) (Pos b)

We define a type class for incrementing and decrementing NumTypes.
The 'incr' and 'decr' functions correspond roughly to HList's 'hSucc'
and 'hPred' respectively.

> class (NumTypeI a, NumTypeI b) => Succ a b | a -> b, b -> a

To increment NumTypes we either prepend 'Pos' to numbers greater
than or equal to Zero or remove a 'Neg' from numbers less than Zero.

> instance Succ Zero (Pos Zero)
> instance (PosTypeI a) => Succ (Pos a) (Pos (Pos a))
> instance Succ (Neg Zero) Zero
> instance (NegTypeI a) => Succ (Neg (Neg a)) (Neg a)


= Addition and subtraction =

Now let us move on towards more complex arithmetic operations. We
define a class for addition and subtraction of NumTypes.

> class (Add a b c, Sub c b a)
>    => Sum a b c | a b -> c, a c -> b, b c -> a

In order to provide instances satisfying the functional dependencies
of 'Sum', in particular the property that any two parameters uniquely
define the third, we must use helper classes.

> class (NumTypeI a, NumTypeI b, NumTypeI c) => Add a b c | a b -> c
> class (NumTypeI a, NumTypeI b, NumTypeI c) => Sub a b c | a b -> c

Adding anything to Zero gives "anything".

> instance (NumTypeI a) => Add Zero a a

When adding to a non-Zero number our strategy is to "transfer" type
constructors from the first type to the second type until the first
type is Zero. We use the 'Succ' class to do this.

> instance (PosTypeI a, Succ b c, Add a c d) => Add (Pos a) b d
> instance (NegTypeI a, Succ c b, Add a c d) => Add (Neg a) b d

We define our helper class for subtraction analogously.

> instance (NumType a) => Sub a Zero a
> instance (Succ a' a, PosTypeI b, Sub a' b c) => Sub a (Pos b) c
> instance (Succ a a', NegTypeI b, Sub a' b c) => Sub a (Neg b) c

While we cold have defined a single 'Sub' instance using negation and
addition.

] instance (Negate b b', Add a b' c) => Sub a b c

However, the constraints of such a 'Sub' instance which are not
also constraints of the 'Sub' class can complicate type signatures
(is this true or was I confused by other issues at the time?). Thus
we elect to use the lower level instances analoguous to the 'Add'
instances.

Using the helper classes we can provide an instance of 'Sum' that
satisfies its functional dependencies. We provide an instance of
'Sum' in terms of 'Add' and 'Sub'.

> instance (Add a b c, Sub c b a, Sub c a b) => Sum a b c


= Division =

We will do division on NumTypes before we do multiplication. This
may be surprising but it will in fact simplify the multiplication.
The reason for this is that we can have a "reverse" functional
dependency for division but not for multiplication. Consider the
expressions "x / y = z". If y and z are known we can always determine
x. However, in "x * y = z" we can not determine x if y and z are
zero.

The 'NonZeroI' class is used as a constraint on the denominator 'b'
in our 'Div' class.

> class (NumTypeI a, NonZeroI b, NumTypeI c) => Div a b c | a b -> c, c b -> a

Zero divided by anything (we don't bother with infinity) equals
zero.

> instance (NonZeroI n) => Div Zero n Zero

Note that We could omit the 'NonZeroI' class completely and instead
provide the following two instances.

] instance (PosTypeI n) => Div Zero (Pos n) Zero
] instance (NegTypeI n) => Div Zero (Neg n) Zero

Going beyond zero numbers we start with a base case with all numbers
positive. We recursively subtract the denominator from nominator
while incrementing the result, until we reach the zero case.

> instance ( Sum n' (Pos n'') (Pos n)
>          , Div n'' (Pos n') n''', PosTypeI n''')
>       => Div (Pos n) (Pos n') (Pos n''')

Now we tackle cases with negative numbers involved. We trivially
convert these to the all-positive case and negate the result if
appropriate.

> instance ( NegTypeI n, NegTypeI n'
>          , Negate n p, Negate n' p'
>          , Div (Pos p) (Pos p') (Pos p'')
>          , PosTypeI p'')
>       => Div (Neg n) (Neg n') (Pos p'')
> instance ( NegTypeI n, Negate n p'
>          , Div (Pos p) (Pos p') (Pos p'')
>          , Negate (Pos p'') (Neg n'')
>          , PosTypeI p)
>       => Div (Pos p) (Neg n) (Neg n'')
> instance ( NegTypeI n, Negate n p'
>          , Div (Pos p') (Pos p) (Pos p'')
>          , Negate (Pos p'') (Neg n'')
>          , PosTypeI p)
>       => Div (Neg n) (Pos p) (Neg n'')


= Multiplication =

Class for multiplication. Limited by the type checker stack. If the
multiplication is too large this error message will be emitted:

    Context reduction stack overflow; size = 20
    Use -fcontext-stack=N to increase stack size to N

> class (NumTypeI a, NumTypeI b, NumTypeI c) => Mul a b c | a b -> c

Providing instances for the 'Mul' class is really easy thanks to
the 'Div' class having the functional dependency "c b -> a".

> instance (NumTypeI n) => Mul n Zero Zero
> instance (NumTypeI a, NumTypeI c, PosTypeI p, Div c (Pos p) a) => Mul a (Pos p) c
> instance (NumTypeI a, NumTypeI c, NegTypeI n, Div c (Neg n) a) => Mul a (Neg n) c


= Functions =

Using the above type classes we define functions for various
arithmetic operations. All functions are undefined and only operate
on the type level. Their main contribution is that they facilitate
NumType arithmetic without explicit (and tedious) type declarations.

The main reason to collect all functions here is to keep the
preceeding sections free from distraction.

> negate :: (Negate a b) => a -> b
> negate _ = undefined

> incr :: (Succ a b) => a -> b
> incr _ = undefined
> decr :: (Succ a b) => b -> a
> decr _ = undefined

> (+) :: (Sum a b c) => a -> b -> c
> _ + _ = undefined
> (-) :: (Sum a b c) => c -> b -> a
> _ - _ = undefined

> (/) :: (Div a b c) => a -> b -> c
> _ / _ = undefined

> (*) :: (Mul a b c) => a -> b -> c
> _ * _ = undefined


= Convenince types and values =

Finally we define some type synonyms for the convenience of clients
of the library.

> type Pos1 = Pos Zero
> type Pos2 = Pos Pos1
> type Pos3 = Pos Pos2
> type Pos4 = Pos Pos3
> type Pos5 = Pos Pos4
> type Neg1 = Neg Zero
> type Neg2 = Neg Neg1
> type Neg3 = Neg Neg2
> type Neg4 = Neg Neg3
> type Neg5 = Neg Neg4

Analogously we also define some convenience values (all 'undefined'
but with the expected types).

> zero :: Zero  -- ~ hZero
> zero = undefined
> pos1 :: Pos1
> pos1 = incr zero
> pos2 :: Pos2
> pos2 = incr pos1
> pos3 :: Pos3
> pos3 = incr pos2
> pos4 :: Pos4
> pos4 = incr pos3
> pos5 :: Pos5
> pos5 = incr pos4
> neg1 :: Neg1
> neg1 = decr zero
> neg2 :: Neg2
> neg2 = decr neg1
> neg3 :: Neg3
> neg3 = decr neg2
> neg4 :: Neg4
> neg4 = decr neg3
> neg5 :: Neg5
> neg5 = decr neg4


= References =

[1] http://homepages.cwi.nl/~ralf/HList/
[2] http://okmij.org/ftp/Computation/resource-aware-prog/BinaryNumber.hs