The wonders of technology
Inspired by a colleague, I've taken up Haskell again. To do so, I've been re-reading Learn You a Haskell. I have the ebook on my iPad, along with an app called Raskell. Raskell is a "Haskell development environment for your iPad"; naturally, it's a little quirky due to the form factor but it works well for what it does. It means that I can do this
I can also switch quickly back and forth (or just bring up a small preview of the page I'm in the book to quickly verify something):
As can be seen in the screenshot, I'm quite enamoured of Literate
Haskell. One of my favourite things about Go is godoc, and it appears
that Literate Haskell (in conjunction with pandoc) will let me
produce quality code illustrations will less effort, as well as improve
documentation in general.
The nice thing about Literate Haskell is that the rest of this post is an
example of a Literate Haskell program
that was converted to markdown using pandoc 08typeclass.lhs -o
08typeclass.md.
Making Our Own Types and Typeclasses
Types are defined using the data keyword like so:
data Bool' = False | True
More complex types may also be defined:
-- Circle x y r
-- Rectangle x1 y1 x2 y2
data Shape = Circle Float Float Float |
Rectangle Float Float Float Float
deriving Show
The deriving Show declaration allows the types to be displayed, eg. given
niceRectangle :: Shape
niceRectangle = Rectangle 0 0 3 4
Without show, calling niceRectangle from the REPL will trigger an error:
ghci> niceRectangle
ERROR - Cannot find "show" function for:
*** Expression : niceRectangle
*** Of type : Shape
With show, calling this from the REPL will show something to the effect of
ghci> niceRectangle
Rectangle 0.0 0.0 3.0 4.0
The value constructors (Circle and Rectangle) may be used in pattern matching.
surface :: Shape -> Float
surface (Circle _ _ r) = pi * r^2
surface (Rectangle x1 y1 x2 y2) = (abs $ x1 - x2) + (abs $ y1 - y2)
It's important to understand the distinction between Shape and Circle. Shape is a type, and Circle is a function producing a value of type Shape. The fact that a value constructor is a function means it can be partially applied. This leads to a origin concentric circle function:
originConcentricCircle :: Float -> Shape
originConcentricCircle = Circle $ Point 0 0
This new function can be used to generate a sequence of concentric circles, a la
ghci> map originConcentricCircle [1..10]
[Circle (Point 0.0 0.0) 1.0
,Circle (Point 0.0 0.0) 2.0
,Circle (Point 0.0 0.0) 3.0
,Circle (Point 0.0 0.0) 4.0
,Circle (Point 0.0 0.0) 5.0
,Circle (Point 0.0 0.0) 6.0
,Circle (Point 0.0 0.0) 7.0
,Circle (Point 0.0 0.0) 8.0
,Circle (Point 0.0 0.0) 9.0
,Circle (Point 0.0 0.0) 10.0]
The Shape type may be refined further:
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float |
Rectangle Point Point
deriving (Show)
This does require changes to surface.
surface :: Shape -> Float
surface (Circle _ r) = pi * r^2
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x1 - x2) + (abs $ y1 - y2)
These value constructors could be exported via
module Shapes
(Point(..)
,Shape(..)
,surface)
where
Alternatively, to just allow the use of Shapes via auxilliary functions,
export just Shape and the desired functions:
module Shapes
(Shape
,originConcentricCircle)
where
Record syntax
Instead of a tedious declaration such as
-- first name, last name, age, height, phone number
data TediousPerson = TediousPerson String String Int Float String deriving (Show)
printTediousName :: TediousPerson -> String
printTediousName (TediousPerson f l _ _ _) = f ++ " " ++ l
Instead, define a Person as a record:
data Person = Person { firstName :: String
,lastName :: String
,age :: Int
,height :: Float
,phoneNumber :: String
} deriving (Read, Show, Eq)
This defines, for example, a function firstName:
ghci> :t firstName
firstName :: Person -> String
The author:
me :: Person
me = Person "Kyle" "Isom" 27 69 ""
Alternatively,
meToo :: Person
meToo = Person{firstName="Kyle"
,lastName="Isom"
,age=27
,height=69
,phoneNumber=""
}
A function for printing names using the record functions:
printName :: Person -> String
printName p = (firstName p) ++ " " ++(lastName p)
Type parameters
Consider a vector:
data Vector a = Vector a a a deriving (Show)
A few operations defined on vectors:
vplus :: (Num t) => Vector t -> Vector t -> Vector t
(Vector i j k) `vplus` (Vector l m n) = Vector (i+l) (j+m) (k+n)
vectMult :: (Num t) => Vector t -> t -> Vector t
(Vector i j k) `vectMult` m = Vector (i*m) (j*m) (k*m)
scalarMult :: (Num t) => Vector t -> Vector t -> t
(Vector i j k) `scalarMult` (Vector l m n) = i*l + j*m + k*n
Derived instances
Haskell can often automatically derive support for
- Eq
- Show
- Read
- Ord
- Enum
- Bounded
Typically, a record's fields must satisfy the typeclass as well. In the
Person type, all of its fields are members of the Eq typeclass,
which allows the Person type to automatically derive Eq.
The read function provided by members of the Read type class converts
a string to some value:
ghci> :t read
read :: Read a => String -> a
It does require explicit type annotation if the value can't be inferred from context.
ghci> read "5.0"
ERROR - Unresolved overloading
*** Type : Read a => a
*** Expression : read "5.0"
ghci> read "5.0" :: Float
5.0
We could read a Person:
readPerson :: Person
readPerson = read "Person {firstName=\"Kyle\", lastName=\"Isom\", age=27, height=69, phoneNumber=\"\"}"

