This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Real World Haskell: Lecture 4 Bryan OSullivan 2009-10-29
Quaternions A quaternion is a mathematical structure, the
extension of complex numbers to three dimensions. In mathematical
notation, some people write a quaternion q as follows: q = a + bi +
cj + dk The i, j, and k terms (the basis elements) are related to
the of complex numbers, and have similarly weird properties: i2 =
j2 = k2 = ijk = 1
What are quaternions for? Invented in 1843 by William Rowan
Hamilton. Briey used in 19th-century physics, e.g. in the
description of Maxwells equations that related electric and
magnetic elds to charge density and current density. Lost favour in
the 1890s, supplanted by vector analysis. Recently popular again,
due to utility in describing rotations. Current uses include:
Computer graphics (smoothly interpolated rotations) Signal
processing (image disparity, e.g. in geophysics) Attitude control
(e.g. in spacecraft) Orbital mechanics Simulation of molecular
dynamics
Whats this got to do with Haskell? The set H of quaternions is
equal to R4 (a 4d vector space over the reals), so we could
conceivably use a representation like this: type Q u a t e r n i o
n = ( Double , Double , Double , Double ) Quaternions are not
arbitrary 4-vectors; they have special properties. So why might we
not want to use this denition? Since Quaternion is simply a synonym
for a 4-tuple of Doubles, the compiler will treat the two types as
the same, even when our intended meaning is distinct.
Giving types more structure We conclude that we want a
quaternion to somehow look dierent from a 4-tuple. Heres how we can
do it: data Q u a t e r n i o n = Q Double Double Double Double The
data directive introduces a completely new type named Quaternion.
We call the name that follows the data directive the type
constructor: it identies the type.
Data constructors data Q u a t e r n i o n = Q Double Double
Double Double Following the = sign above is the name Q, which is a
data constructor for the Quaternion type. We use a data constructor
to build a new value of a given type. Type constructors create
types. Data constructors create values.
Using constructors (I) The 4 instances of Double following the
Q are the type parameters to that data constructor. When we use our
data constructor, we need to supply values: The number of values
must match the number of type parameters. The type of each value
must match the type parameter at that position. We have a simple
case here: just plug in four numbers. zero = Q 0 0 0 0
Using constructors (II) Remember the data constructors for
lists? (:) : : a > [ a ] > [ a ] [] :: [a]
Using constructors (II) Remember the data constructors for
lists? (:) : : a > [ a ] > [ a ] [] :: [a] We use data
constructors to create new values. foo = f : o : o : [ ]
Using constructors (II) Remember the data constructors for
lists? (:) : : a > [ a ] > [ a ] [] :: [a] We use data
constructors to create new values. foo = f : o : o : [ ] We use
type constructors in type signatures. stripLeft : : [ Char ] > [
Char ]
Using constructors (II) Remember the data constructors for
lists? (:) : : a > [ a ] > [ a ] [] :: [a] We use data
constructors to create new values. foo = f : o : o : [ ] We use
type constructors in type signatures. stripLeft : : [ Char ] > [
Char ] We pattern match against data constructors to inspect a
value. s t r i p L e f t ( x : xs ) | isSpace x = s t r i p L e f t
xs | otherwise = x : xs stripLeft [] = []
Using data constructors (III) We do the same with our new
Quaternion type. Heres a type signature for the function that
creates a scalar-valued quaternion. Notice the use of the type
constructor. s c a l a r : : Double > Q u a t e r n i o n And
heres the denition of the scalar function, where we use the data
constructor to create a new value of type Quaternion. scalar s = Q
s 0 0 0
Using data constructors (IV) We perform pattern matching as you
might expect: s c a l a r P a r t : : Q u a t e r n i o n >
Double scalarPart (Q s ) = s The special pattern above has the
following meaning: I dont care whats at this position. Dont inspect
the value here, and dont bind it to a name. In other words, its a
wild card.
Using data constructors (IV) We perform pattern matching as you
might expect: s c a l a r P a r t : : Q u a t e r n i o n >
Double scalarPart (Q s ) = s The special pattern above has the
following meaning: I dont care whats at this position. Dont inspect
the value here, and dont bind it to a name. In other words, its a
wild card. Notice that we had to provide four patterns after the Q,
one to match each of the Q constructors parameters.
Spelunking with the interpreter We can usefully inspect our new
type in ghci: Prelude> :info Quaternion data Quaternion = Q
Double Double Double Double -- Defined at Quaternion.hs:1:5-14 The
:info command is very useful! It tells us everything ghci knows
about an identier (regardless of whether its a type, a function, or
whatever).
Spelunking with the interpreter We can usefully inspect our new
type in ghci: Prelude> :info Quaternion data Quaternion = Q
Double Double Double Double -- Defined at Quaternion.hs:1:5-14 The
:info command is very useful! It tells us everything ghci knows
about an identier (regardless of whether its a type, a function, or
whatever). We can also inspect the type of the Q data constructor:
Prelude> :type Q Q :: Double -> Double -> Double ->
Double -> Quaternion
What does this correspond to? Are you struggling to follow so
far?
What does this correspond to? Are you struggling to follow so
far? Heres an approximate equivalent to what we just covered, in a
familiar language: struct quaternion { double a, b, c, d; };
What does this correspond to? Are you struggling to follow so
far? Heres an approximate equivalent to what we just covered, in a
familiar language: struct quaternion { double a, b, c, d; }; And
heres another, in a dierent (but still familiar) language: class
Quaternion(object): def __init__(self, a,b,c,d):
self.a,self.b,self.c,self.d = a,b,c,d
Comparing quaternions Were used to being able to compare
values. ( f o o == b a r , baz /= quux ) == ( False , True ) >
So how should we compare quaternions? This denition wont compile: w
t f = s c a l a r 1 == s c a l a r 2 Ugh, okay. Lets try this: e q
u a l s (Q a b c d ) (Q w x y z ) = a == w && b == x
&& c == y && d == z
Notational aside: functions and operators Functions and
operators are the same, save for xity. We usually apply functions
in prex position, and operators in inx position. However, we can
treat an operator as a function by wrapping it in parentheses: (+)
1 2 (:) a [ ] zipWith (+) [ 1 , 2 , 3 ] [ 4 , 5 , 6 ] We can also
apply a function as if it was an operator, by wrapping it in
backquotes: s c a l a r 1 equals scalar 2 wtf isPrefixOf
wtfbbq
But still. . . ugh! It seems lame that we can write this for
strings: f o o == b a r And, even with our shiny new inx syntax, nd
ourselves stuck writing code like this for quaternions: s c a l a r
2 equals s c a l a r 3.14159265 Surely theres some kind of unifying
principle we could put to work?
Enter the typeclass Here is a denition of a widely used
typeclass: c l a s s Eq a where (==) : : a > a > Bool The
denition says there is a class of types that support a function
named (==).
Enter the typeclass Here is a denition of a widely used
typeclass: c l a s s Eq a where (==) : : a > a > Bool The
denition says there is a class of types that support a function
named (==). How can we declare a type as being a member of this Eq
class? i n s t a n c e Eq Q u a t e r n i o n where (==) = e q u a
l s This means any time someone asks for the (==) function on
Quaternion type, use the equals function.
Use of (==) As we know, we can use the (==) operator when
writing functions: elem a [ ] = False elem a ( x : x s ) | a == x =
True | o t h e r w i s e = elem a x s Notice something very
important: This function needs to know almost nothing about the
type of the values a and x. Its enough to know that the type
supports the (==) function, i.e. that its an instance of the Eq
typeclass.
Bounded polymorphism The elem function is clearly polymorphic,
but its not fully polymorphic. It will work for any instance of Eq.
However, it will not work on types that are not instances of Eq,
since they dont supply an implementation of (==). So our function
exhibits a special kind of polymorphism called bounded (or
constrained) polymorphism.
Expressing bounded polymorphism We can express the fact that
elem only accepts instances of Eq in its type signature: elem : : (
Eq a ) = a > [ a ] > Bool > The (Eq a) => part of the
signature is called a constraint. In the absence of a type
signature, a Haskell compiler will infer the presence of any
necessary constraints.
Expressing bounded polymorphism We can express the fact that
elem only accepts instances of Eq in its type signature: elem : : (
Eq a ) = a > [ a ] > Bool > The (Eq a) => part of the
signature is called a constraint. In the absence of a type
signature, a Haskell compiler will infer the presence of any
necessary constraints. Aside: Haskells kind of polymorphism is
called parametric. In this type signature: (++) : : [ a ] > [ a
] > [ a ] The type of (++) is parameterized by the type variable
a.
Rainbows, oh my! Suppose we want to refer to symbolic values.
data Rainbow = Red | Orange | Y e l l o w | Green | Blue | I n d i
g o | V i o l e t We have dened a type Rainbow with a whole pile of
data constructors. These constructors are all independent. If youre
a C or C++ hacker, think of Rainbow as an enumerated type (enum).
Each data constructor creates a value of type Rainbow. We can
pattern match against a value to see whether it is Green, or Blue,
or whatever.
Showing your work We havent indicated how to print values from
our new datatypes yet. Indeed, unless we do something explicit, we
cant print them. Fortunately for us, theres a Show typeclass, with
a useful function: show : : (Show a ) = a > S t r i n g > So.
What do we do? i n s t a n c e Show Rainbow where show Red = Red
show Green = Green { . . . e t c . . . }
Yeuch! Thats some serious drudgery there. I hate repeating
myself. Fortunately, for some key typeclasses, Haskell can
automatically derive instances for us as follows. data Rainbow =
Red | Orange | Y e l l o w | Green | Blue | I n d i g o | V i o l e
t d e r i v i n g ( Eq , Ord , Bounded , Enum, Show) This saves a
lot of meaningless nger-plonkulating.
Homework (I)the trivial part Use ghci to investigate the Ord,
Bounded, and Enum typeclasses.
Homework (II) Three non-overlapping points in the plane R2 form
either an acute angle (< 180 ), a straight line, or an obtuse
angle (> 180 ). Dene a type that represents these possibilities.
Write a type signature for a function that accepts three points,
and categorises them in this fashion. Now write the function
itself.
Homework (III) In computational geometry, the convex hull of a
set of non-overlapping points is the minimal bounding polygon for
which all angles are acute. Using your categorisation function,
write a function that computes the convex hull of a set of points.
(If you need a hint, look up Grahams scan algorithm.)