2. Adding quaternions
We add two quaternions by adding their coefficients.
a+ bi + cj + dk
+ w+ xi + yj + zk
= (a + w ) + (b + x)i + (c + y )j + (d + z)k
Or, in Haskell:
addQ (Q a b c d ) (Q w x y z ) =
Q ( a+w) ( b+x ) ( c+y ) ( d+z )
3. Subtracting quaternions
Subtraction is defined similarly.
subQ (Q a b c d ) (Q w x y z ) =
Q ( a−w) ( b−x ) ( c−y ) ( d−z )
4. Typeclass inheritance
The Eq typeclass that we met last week lets us compare values for
equality.
For many values, we want to be able to compare them for (total)
ordering, for which we use the Ord typeclass.
c l a s s ( Eq a ) = Ord a where
>
compare : : a −> a −> Ordering
Notice the constraint on the Ord class: this states that in order for
a type to be an instance of Ord, it must already be an instance of
Eq.
5. The Num typeclass
Haskell defines a typeclass named Num that lets us express
common arithmetic operations:
c l a s s ( Eq a , Show a ) = Num a where
>
(+) : : a −> a −> a
(∗) : : a −> a −> a
(−) : : a −> a −> a
negate : : a −> a
abs : : a −> a
{− e t c . −}
6. Num and quaternions
On first glance, we can easily fit our Quaternion type into the
Num class.
i n s t a n c e Num Q u a t e r n i o n where
(+) = addQ
(−) = subQ
{− ??? −}
We quickly run into problems: it’s not obvious what negate or abs
should do.
Maybe we can do something with multiplication, though?
7. Multiplying quaternions
If we can remember the identities
i2 = j2 = k2 = ijk = −1
Then multiplication of quaternions falls out from the usual laws of
arithmetic, but takes a complicated form:
a + bi + cj + dk ∗ w + xi + y j + zk = aw − bx − cy − dz
+ (ax + bw + cz − dy )i
+ (ay − bz + cw + dx)j
+ (az + by − cx + dw )k
8. Multiplying quaternions in Haskell
It’s easy to convert our equation into executable form:
mulQ (Q a b c d ) (Q w x y z )
= Q ( a ∗w − b∗ x − c ∗ y − d∗ z )
( a ∗ x + b∗w + c ∗ z − d∗ y )
( a ∗ y − b∗ z + c ∗w + d∗ x )
( a ∗ z + b∗ y − c ∗ x + d∗w)
9. Multiplying quaternions in Haskell
It’s easy to convert our equation into executable form:
mulQ (Q a b c d ) (Q w x y z )
= Q ( a ∗w − b∗ x − c ∗ y − d∗ z )
( a ∗ x + b∗w + c ∗ z − d∗ y )
( a ∗ y − b∗ z + c ∗w + d∗ x )
( a ∗ z + b∗ y − c ∗ x + d∗w)
Does this mean that we should augment our definition of Num as
follows?
i n s t a n c e Num Q u a t e r n i o n where
( ∗ ) = mulQ
{− e t c . −}
10. Arithmetic laws
There are some laws that are so ingrained into our minds that we
never think about them:
m+n =n+m (commutative law of addition)
(m + n) + k = m + (n + k) (associative law of addition)
mn = nm (commutative law of multiplication)
(mn)k = m(nk) (associative law of multiplication)
11. Laws for quaternions
We can see by simple inspection that addition over quaternions
must satisfy the commutative and associative laws of normal
arithmetic.
We used those familiar arithmetic laws to derive the formula for
quaternion multiplication, but do quaternions satisfy the
commutative law of multiplication?
Prelude> let a = Q 2 0 9 0
Prelude> let b = Q 0 9 0 2
Prelude> a ‘mulQ‘ b
Q 0.0 36.0 0.0 (-77.0)
Prelude> b ‘mulQ‘ a
Q 0.0 0.0 0.0 85.0
12. Laws: made to be broken?
When you write or use a typeclass, there’s an implied
understanding that you’ll obey its laws1 .
Code that uses Eq relies on the fact that if a == b is True, then
b == a will be True too, and a /= b will be False.
Similarly, code that uses Num implicitly relies on the
commutativity and associativity of addition and multiplication.
1
Unfortunately, these laws are often undocumented.
13. Laws: made to be broken?
When you write or use a typeclass, there’s an implied
understanding that you’ll obey its laws1 .
Code that uses Eq relies on the fact that if a == b is True, then
b == a will be True too, and a /= b will be False.
Similarly, code that uses Num implicitly relies on the
commutativity and associativity of addition and multiplication.
Neither the type system nor any other aspect of Haskell will help
you to do the heavy lifting here:
The burden is on you, the creator of a type, to ensure that if
you make it an instance of a typeclass, that it follows the laws.
1
Unfortunately, these laws are often undocumented.
14. A sketchy approach
Since quaternion multiplication is not commutative, we should not
implement (∗). But what more should we do?
For instance, we could partially implement Num:
i n s t a n c e Num Q u a t e r n i o n where
(+) = addQ
( ∗ ) = undefined
What effect does this have on code that tries to use multiplication?
Prelude> scalar 2 * scalar 3
*** Exception: Prelude.undefined
This is not very satisfactory behaviour.
15. Playing fast and loose
Of course, Haskell itself doesn’t escape the sin bin. What happens
to those fancy laws in the presence of inexact arithmetic?
Prelude> let a = 1e20 :: Double
Prelude> (a + (-a)) + 1
1.0
Prelude> a + ((-a) + 1)
0.0
(This is the same behaviour as every other language that
implements floating point, by the way.)
A conclusion? You can violate the rules, but the compiler can’t
remind you that you’re cheating.
16. Code that might fail
You’ve probably seen this behaviour by now:
Prelude> 1 ‘div‘ 0
*** Exception: divide by zero
These exceptions are often annoying, because we can’t easily catch
and handle them.
There exists a predefined type we can use to deal with these cases:
data Maybe a = Nothing
| Just a
Notice that this type is parameterized, so we can have types such
as Maybe Int, or Maybe (String, Bool), or so on.
17. Safer functions via Maybe
Safer integer division:
a ‘ s a f e D i v ‘ 0 = Nothing
a ‘ s a f e D i v ‘ b = Just ( a ‘ div ‘ b )
A safer version of head:
safeHead [ ] = Nothing
s a f e H e a d ( x : ) = Just x
18. Exercise time!
You should be familiar with the map function by now:
map : : ( a −> b ) −> [ a ] −> [ b ]
Write the equivalent function for the Maybe type:
mapMaybe : : ( a −> b ) −> Maybe a −> Maybe b
19. Binary trees
data Tree a = Empty
| Node a ( Tree a ) ( Tree a )
d e r i v i n g ( Eq , Ord , Show)
l e a f v = Node v Empty Empty
someOldTree =
Node ( l e a f ” f o o ” )
( Node ( l e a f ” b a r ” )
( Node ( l e a f ” baz ” ) Empty ) )
20. Sizing a tree
s i z e Empty = 0
s i z e Node a b = 1 + s i z e a + s i z e b
21. Mapping again
What should this function look like?
mapTree : : ( a −> b ) −> Tree a −> Tree b
22. Generalising mapping
So far, we’ve seen three different container types, with three
different map-like functions:
map : : ( a −> b ) −> [ a ] −> [ b ]
mapMaybe : : ( a −> b ) −> Maybe a −> Maybe b
mapTree : : ( a −> b ) −> Tree a −> Tree b
It turns out we can write a typeclass to generalise this idea:
c l a s s Functor f where
fmap : : ( a −> b ) −> f a −> f b
i n s t a n c e Functor Maybe where
fmap = mapMaybe
23. Homework—binary search trees
Turn the Tree type into a binary search tree by defining the
following functions:
i n s e r t : : ( Ord a ) = a −> Tree a −> Tree a
>
c o n t a i n s : : ( Ord a ) = a −> Tree a −> Bool
>
24. Homework—key/value containers
Adapt your binary search tree code for use to create a simple
key/value container:
type Map a b = Tree ( a , b )
insertItem
: : ( Ord a ) = a −> b −> Map a b −> Map a b
>
lookupByKey
: : ( Ord a ) = a −> Map a b −> Maybe b
>
listToMap
: : ( Ord a ) = [ ( a , b ) ] −> Map a b
>
mapToList
: : Map a b −> [ ( a , b ) ]
minItem
: : ( Ord a ) = Map a b −> Maybe ( a , b )
>