-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path4.Arithmetic.lhs
More file actions
144 lines (96 loc) · 3.41 KB
/
4.Arithmetic.lhs
File metadata and controls
144 lines (96 loc) · 3.41 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
All the stuff we need from before:
> {-# LANGUAGE MultiParamTypeClasses #-}
> {-# LANGUAGE FlexibleInstances #-}
> {-# LANGUAGE FunctionalDependencies #-}
> {-# LANGUAGE UndecidableInstances #-}
> import Prelude hiding (even, odd, pred)
> data True
> data False
> data Zero
> data Succ n
> type Three = Succ (Succ (Succ Zero))
But here we redefine the classes with functional dependencies,
preventing us from defining both (Even Zero True) and (Even Zero
False), for example.
> class Even n b | n -> b where
> even :: n -> b
> class Odd n b | n -> b where
> odd :: n -> b
The '|' says that the class is constrained by n -> b, in other words
b is uniquely determined by n. You can read '|' here as 'such that'.
Note: This is also why we need 'UndecidableInstances' above, as it is
now not guaranteed that any type-level computation will terminate.
> instance Even Zero True
> instance Odd n b => Even (Succ n) b
> instance Odd Zero False
> instance Even n b => Odd (Succ n) b
try this out in ghci with the following:
:type even (undefined :: Three)
:type odd (undefined :: Three)
Notice we don't need to specify the whole type due to the functional
dependency.
Arithmetic
==========
If we define arithmetic on values we end up with functions that look
like this:
add Zero b = b
add (Succ a) b = Succ (add a b)
mul Zero b = b
mul (Succ a) b = add b (mul a b)
These can be 'lifted' to the type level as follows:
Addition
--------
The class instance says a and b uniquely determine c
> class Add a b c | a b -> c where
> add :: a -> b -> c
The instances say anything plus zero is itself
> instance Add Zero b b
and if I can add a and b to get c, then adding (a + 1) + b = c + 1
> instance Add a b c => Add (Succ a) b (Succ c)
Multiplication
--------------
Again, a and b uniquely determine c
> class Mul a b c | a b -> c where
> mul :: a -> b -> c
Anything times zero is zero
> instance Mul Zero b Zero
If a * b = c, and b + c = d, then (a + 1) * b = d
> instance (Mul a b c, Add b c d) => Mul (Succ a) b d
Let's also define an alias for undefined to make typing easier:
> u = undefined
Try the following in ghci:
:t add (u::Three) (u::Three)
:t mul (u::Three) (u::Three)
Power
-----
Static computations are those performed by the compiler. Dynamic
computations are run-time computations.
We can illustrate combining these with the power function. The typical
value-level power function is as follows (with Zero and Succ as
values, not types):
data Nat
= Zero | Succ Nat
deriving (Show, Eq)
pow b Zero = one
pow b (Succ n) = mul b (pow b n)
The static version looks like this:
> type One = Succ Zero
> class Pow a b c | a b -> c where
> pow :: a -> b -> c
Anything to the zero power is one, and if a ** b = c and a * c = d,
then a * c = a * a ** b = a ** (b + 1)
> instance Pow a Zero One
> instance (Pow a b c, Mul a c d) => Pow a (Succ b) d
If we use Int for the base, we can ensure the base is calculated at
runtime while the exponent is determined at compile-time:
> class Pred a b | a -> b -- Predecessor relation
> where pred :: a -> b
> instance Pred (Succ n) n
> class Power n
> where power :: Int -> n -> Int
> instance Power Zero
> where power _ _ = 1 -- Anything to the zero is one
> instance Power n => Power (Succ n)
> where power x n = x * power x (pred n)
An example:
power 2 (mul (u :: Three) (u :: Three))