Haskell Notes
This is a short collection of notes for a tutorial on Haskell presented at group lunch. It follows a presentation by Yuhong Xiong on ML the previous week.
About Haskell
Haskell is a lazy, strongly-typed functional programming language. If you've never used a modern functional language, you might be surprised at some of things it can do.
- The main Haskell page is at www.haskell.org.
- The simplest version of Haskell to get started with is Hugs. If you are running on Windows, get the Windows binary distribution.
- There are quite a lot of libraries and projects using Haskell. In particular, check out:
Instructions
The examples in these notes are contained in Haskell source files, linked from this page. Run the hugs binary, or if you are on Windows, run the winhugs binary -- it is basically the same thing but allows you to browse the type hierarchy. At the command prompt, change to the directory containing the example files. For example
:cd c:/html/haskell/The examples are of two kinds. Expressions can be input on the Hugs command line, as in
Prelude> map (+1) [1, 2, 3]Function definitions must be loaded from a file. For example,
Prelude> :load exampleswill load the example file example.hs.Types
Haskell has a comprehensive set of types. Some examples (type the value to the left of the command line to verify that the interpreter infers the correct type):
7 :: Int 3.1415 :: Float True :: Bool 'z' :: CharLoad the Complex module (with :load Complex) and try1 :+ 2.7 :: Complex Double(More about types later.)
Functions
Functions are defined polymorphically over type classes. Here's a familiar example:
fact n = if n == 0 then 1 else n * fact (n-1)To execute this, type eg fact 4 on the command line. Note that no type was given for this function. To see the type inferred by Haskell, simply type fact on the command line -- you will see that this is a function from Int to Int.There are other features of Haskell that provide more powerful ways of defining functions. This version uses guards:
factg n | n == 0 = 1 | otherwise = n * fact (n - 1)This version uses pattern-matching:factp 0 = 1 factp n = n * factp (n - 1)Patterns
A pattern is formed when a data constructor appears on the left-hand side of a binding. The right-hand side is bound to the corresponding identifiers in the pattern. For example (see the file patt.hs:(x:xs) = [1,2,3,4,5]binds x to 1 and xs to [2,3,4,5]. A lot of useful functions are best defined using patterns:map1 f [] = [] map1 f (x:xs) = f x : map1 f xsFor example, evaluate map (+1) [1,2,3,4,5]. To see the type of map, just type its name at the command prompt.Patterns can also be defined using wild-cards. Here is a more common way of defining map:
map2 f (x:xs) = f x : map2 f xs map2 _ _ = []Higher-order functions and currying
Haskell is implicitly higher-order, meaning that funtions can be accepted as arguments and returned as arguments. The definitions of map above fill this example. In addition, it is curried, meaning that it takes one argument at a time.For example, consider the mpy
mpy :: Num a => a -> a -> a mpy x y = x * yObviously, this multiplies two numbers together. Because of currying, however, the arguments can be supplied one at a time. So,double :: Num a => a -> a double = mpy 2defines the function that multiplies any other number by 2.. Try it with say double 3. Note that we have supplied type signatures here -- without them, Haskell will infer that mpy is a function on Ints, which is more restrictive than we really want.More on types
We can define new data types and functions on them:data CodeRating = Red | Yellow | Green | Blue deriving (Show, Eq) instance Ord CodeRating where compare a b = compare (cix a) (cix b) cix :: CodeRating -> Int cix Red = 0 cix Yellow = 1 cix Green = 2 cix Blue = 3Infinitum
Haskell is lazy, which means that it evaluates expressions only when their values are needed. Full laziness is quite a subtle thing, and is not at all what many people assume it to be. (So be careful when you use the term "lazy evaluation"!). One thing you can do is define an infinite sequence:ones = 1 : onesTo see the value, use something liketake 20 oneswhich takes the first 20 elements from the infinite stream. Here's a function that generate an infinite "ramp":ramp value step = value : ramp (value+step) stepWe can easily generate more complex infinite sequences using recursion:square n = take n (repeat 1.0) ++ take n (repeat (-1.0)) ++ square n impulse = 1.0 : repeat 0.0In signal processing, we often want to "buffer" a signal into overlapping segments. This function models that:chop n [] = [] chop n xs = take n xs : chop n (tail xs)Now we can define an FIR filter function. The first argument is the coefficient vector (backwards), while the second is the infinite stream to be filtered.fir hs xs = map (ip hs) (chop n (take (n-1) (repeat 0.0) ++ xs)) where ip as bs = sum (zipWith (*) as bs) n = length hsTry, for example:take 20 (fir [0.1, 0.9, 1.0, 0.9, 0.1] impulse)andtake 20 (fir [0.1, 0.9, 1.0, 0.9, 0.1] (square 5))