Haskell

Haskell is a purely-functional, statically typed programming language. Haskell is elegant, concise, and lazy. Haskell can be compiled (ghc) or interpreted (ghci). Haskell files use the .hs extension. Information on this page is taken from Learn You a Haskell for Great Good!

Functions

Functions in Haskell are pure. Pure functions have no side-effects. If a function is called twice with the same parameters, it's guaranteed to return the same result (referential transparency).

Infix Functions

Infix functions (in contrast to prefix functions) are sandwiched between their parameters.

-- Arithmetic
50 * 100 - 4999
5 / 2

-- Boolean algebra
not (False || True && True)

-- Equality tests
5 == 5
5 /= 10

Prefix Functions

In Haskell, prefix functions are called by writing the function name, a space and then the parameters, separated by spaces. Two parameter functions may be called as an infix function using backticks (e.g. 92 `div` 10). Note that function application has highest precedence (e.g. succ 9 * 10 <==> (succ 9) * 10).

succ 8 -- 9
min 9 10 -- 10

Writing Functions

doubleMe x = x + x

conanO'Brien = "It's a-me, Conan O'Brien!"

Functions that don't take any parameters are called definitions (or names). We cannot change names and functions once we've defined them.

Control Flow

Haskell's if is an expression instead of a statement. Since expressions must return a value, the else is mandatory in Haskell.

doubleSmallNumber x = if x > 100 then x else x*2

Lists

In Haskell, lists are a homogeneous data structure; list elements must be the same type. Strings are syntactic sugar for character lists.

[1,2,3,4]

-- Concatenation
[1,2,3,4] ++ [9,10,11,12]

-- Cons operator
'A':" SMALL CAT"

-- Indexing
"Steve Buscemi" !! 6

Common list functions include head, tail, last, init (get all but last), length, null (test if empty), reverse, take, drop, maximum, minimum, sum, product, and elem (test for membership).

Ranges

[1..5] -- [1,2,3,4,5]

-- Step
[3,6..15]

-- Infinite lists
take 5 [4,8...]

cycle, repeat, and replicate are useful functions for infinite lists.

List Comprehensions

List comprehensions are similar to set comprehensions in mathematics (e.g. \(S = \{2 \cdot x \mid x \in \mathbb{N}, x \leq 10\}\)). Haskell's list comprehensions can accept any number of lists and predicates.

[x | x <- [50..100], x `mod` 7 == 3]
-- [52,59,66,73,80,87,94]

Tuples

Tuples, unlike lists, don't have to be homogeneous. Tuples also have a specific length defined in their type. Denote tuples with parenthesis ((1,2)). fst and snd are useful functions that operate on pairs. zip is a function that produces pairs.

Types

Haskell has a static type system; the type of every expression is known at compile time. The :t command in ghci gives an expression's type (e.g. "Hi" :: [Char]). :: is read as "has type of."

Type Description
Int Bounded integer.
Integer Unbounded (but less efficient) integer.
Float Single precision floating-point.
Double Double precision floating-point.
Bool Boolean (True or False).
Char Characters denoted by single quotes.

When writing functions, we can choose to given them an explicit type declaration (generally good practice).

removeNonUppercase :: [Char] -> [Char]
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]

Haskell has type inference. However, we must sometimes add a type annotation (e.g. read "5" :: Int).

Type Variables

A type variable is a lowercase symbol that can take any type. Type variables appear in type declarations (e.g. a in head :: [a] -> a). Functions that have type variables are called polymorphic functions.

Typeclasses

A typeclass is a sort of interface that defines some behavior. A class constraint appears in a type declaration and assigns a typeclass. For example the == function has the type declaration (==) :: (Eq a) => a -> a -> Bool.

Typeclass Functions Description
Eq ==, /= Types that support equality testing.
Ord >, <, etc Types that have an ordering.
Show show Types that can be presented as strings.
Read read Opposite of Show.
Enum succ, pred Sequentially ordered types.
Bounded minBound, maxBound Types with an upper and lower bound.
Num +, -, etc Numeric values.
Integral   Only integral (whole) numbers.
Floating   Only floating point numbers.

Pattern Matching

When defining functions, you can define separate bodies for different patterns. Patterns are checked from top to bottom. The program will crash if no patterns match.

factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)

Pattern matching can also be used on tuples (e.g. (x, y)) and lists (e.g. (x:xs))

As patterns let one break up according to a pattern and keep a reference to the whole thing. This is done by putting a name and an @ in front of a pattern. For example, all@(x:xs).

Guards

A guard is a boolean expression indicated with a pipe. A series of guards can appear after a function's name and parameters. If a guard evaluates to True, the corresponding function body is used. The last guard may be otherwise as a catch all. Use guards instead of patterns when testing for a boolean condition.

max' :: (Ord a) => a -> a -> a
max' a b
    | a > b     = a
    | otherwise = b

A where clause can appear after guards. This keyword can define several names and functions which will be visible across the guards. A let, in contrast, won't span across guards. Where bindings are a syntactic construct, lets are expressions.

Let Bindings

A let binding is an expression that defines local variables. The form is let <bindings> in <expression>.

cylinder :: (RealFloat a) => a -> a -> a
cylinder r h =
    let sideArea = 2 * pi * r * h
        topArea = pi * r ^2
    in  sideArea + 2 * topArea

Let bindings can also appear in list comprehensions. Notice that in is omitted.

[bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]

Case Expressions

Case expressions are expressions that perform pattern matching. Note that pattern matching on parameters in function definitions is syntactic sugar for case expressions.

case expression of <pattern> -> <result>
                   <pattern> -> <result>
                   <pattern> -> <result>
                   ...

Higher Order Functions [todo move above]

A higher order function is a function that either that accepts functions as parameters or returns functions. Indicate function parameters with parenthesis:

applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)

All Haskell functions that accept several parameters are curried functions. A function that takes two arguments is really a function that takes one argument and returns another function. If we call a function with too few parameters we get a partially applied function. Infix functions can be partially applied using sections. To section an infix function, simply surround it with parentheses and only supply a parameter on one side.

map, filter, foldl, and foldr are higher order functions. foldl1 and foldr1 are similar to foldl and foldr, but don't require an explicit starting value. They cause runtime errors if called with empty lists. scanl and scanr are also similar to foldl and foldr. They report all the intermediate accumulator states in the form of a list.

Lambda Expressions

To make a lambda, write a \, parameters, ->, and the function body. Lambdas are normally surrounded by parenthesis unless we mean for them to extend all the way to the right.

\x y -> x + y

Function Application

$ is an infix function known as function application. Whereas normal function application (putting a space between two things) has a really high precedence, the $ function has the lowest precedence. $ can help avoid parenthesis: sum (map sqrt [1..130]) could be written as sum $ map sqrt [1..10].

$ can be treated like any other function:

map ($ 3) [(4+), (10*), (^2), sqrt]

Function Composition

The . function performs function composition. Here's an example.

map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24]

-- Now with composition...
map (negate . abs) [5,-3,-6,7,-3,2,-19,24]

Composition is also convenient for point-free style. Functions in the point-free style avoid mentioning their actual arguments.

sum xs = foldr (+) 0 xs

-- Point-free style!
sum' = foldr (+) 0

Composition is glue to form more complex functions. It allows us to write let fn = f . g . h instead of let fn x = f (g (h x)).

Modules

A Haskell module is a collection of related functions, types and typeclasses. A Haskell program is a collection of modules where the main module loads up the other modules and then uses the functions defined in them to do something. The Haskell standard library is split into modules. See this handy reference. Use import to load a module. In GHCi, load a module with :m.

-- Make Data.List exports available in the global namespace.
import Data.List

-- We can choose not to import certain functions.
import Data.List hiding (nub)

-- Qualified imports help avoid name clashes.
import qualified Data.Map

-- We can also choose to rename the module.
import qualified Data.Map as M

Modules export functions. These functions act as a sort of interface to the module. At the beginning of a module specify the name and function exports.

-- This would be at the top of Geometry.hs
module Geometry
( cubeVolume
, cubeArea
) where

Modules can also be given hierarchical structures. We just need to specify the directory name as part of the module name. For example, the module name could be Geomertry.Cube if we had a Cube.hs file in a Geometry folder.

Custom Types [todo move above]

Use the data keyword to define a type. Here's Bool from the standard library:

data Bool = False | True

The parts after the = are value constructors. The | is read as or. Both the type name and the value constructors have to be capital cased. Value constructors are functions that take parameters and return a value of a data type.

data Point = Point Float Float
data Shape = Circle Point Float | Rectangle Point Point

Circle (Point 0 0) 5

Notice that when defining a point, we used the same name for the data type and the value constructor. This has no special meaning, although it's common if there's only one value constructor.

Exporting Types

To export types from your modules, write the type and parenthesis with value constructors separated by commas. If you want to export all the value constructors, just write ...

module Shapes
( Point(..)
, Shape(..)
) where

You can choose to not export any value constructors by just writing the type in the export statement (e.g. Shape). Data.Map uses this approach. You must use auxiliary functions (like Map.fromList) to create maps.

Record Syntax

Use record syntax when a constructor has several fields and it's not obvious which field is which. Use curly brackets, then the name of the field, a double colon, and the type. Record syntax also makes Haskell automatically create field lookup functions.

data Person = Person { name :: String
                     , age :: Int
                     , height :: Float
                     } deriving (Show)

-- New syntax for constructing values.
Person {name="Elliot", age=26, height=6.0}

Type Parameters

Type constructors take types as parameters to produce new types. Here's an example from the standard library:

data Maybe a = Nothing | Just a

a in this example is a type parameter and Maybe is a type constructor. [] is another example of a type constructor; we must give [] a type parameter (e.g. [Int]). We usually use type parameters when the type that's contained inside the data type's various value constructors isn't really that important for the type to work.

Note that we don't usually put type constraints on the parameters of data declarations, because you'll have to put them into the function type declarations either way.

Derived Instances

Haskell can automatically derive the behavior for Eq, Ord, Enum, Bounded, Show, and Read if we use the deriving keyword when making our type. For example, make a type part of the Show typeclass to allow Haskell to print it. Just add deriving (Show) to the end of a data declaration.

Type Synonyms

Type synonyms just give some types different names so that they make more sense to someone reading our code and documentation. Use the type keyword to create a type synonym. Here's how the standard library defines String as a synonym for [Char].

type String = [Char]

Type synonyms can also be parameterized.

Typeclasses (part 2)

Typeclasses are like interfaces - they define behavior. Here's the definition of Eq in the standard prelude:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
    x == y = not (x /= y)
    x /= y = not (x == y)

class Eq a where means we're defining a new typeclass called Eq. It's not mandatory to implement the function bodies. Here's we've chosen to define them in terms of mutual recursion. Let's try making an instance of Eq.

data TrafficLight = Red | Yellow | Green

instance Eq TrafficLight where
    Red == Red = True
    Green == Green = True
    Yellow == Yellow = True
    _ == _ = False

The class keyword defines new typeclasses and the instance keyword is for making our types instances of typeclasses. Because == was defined in terms of /= and vice versa in the class declaration, we only had to overwrite one of them in the instance declaration. This is the minimal complete definition for the typeclass - the minimum number of functions that we have to implement so that our type can behave like the class advertises.

You can also make typeclasses that are subclasses of other typeclasses. The first part of the class declaration for Num is as follows.

class (Eq a) => Num a where
   ...

Subclassing is really just a class constraint on a class definition.

Input and Output

The putStrLn function has the type declaration putStrLn :: String -> IO (). An I/O action carries out an action with a side effect. This action will be performed when we give it a name of main and run our program.

main = do
    putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn ("Hey " ++ name ++ ", you rock!")

The do syntax glues together several I/O actions into one. By convention, we don't usually specify a type declaration for main. \

The <- construct binds an I/O action's result value to a name. getLine has the type declaration getLine :: IO String. So in the example above, name will have the type String. In Haskell, return makes an I/O action out of a pure value. Return is the opposite of <-. For example return "haha" will have a type IO String. Haskell's return is nothing like the return in most other languages.

To run a program you can either compile it and then run the produced executable file by doing ghc --make helloworld and then ./helloworld or you can use the runhaskell command like so: runhaskell helloworld.hs and your program will be executed on the fly.

Useful functions for dealing with I/O are putStr, putChar, print, getChar, when, sequence, mapM, forM, forever, getContents, and interact.

Here's a program that reads a file and prints the contents:

import System.IO

main = do
    handle <- openFile "girlfriend.txt" ReadMode
    contents <- hGetContents handle
    putStr contents
    hClose handle

We can also do this with the withFile function:

import System.IO

main = do
    withFile "girlfriend.txt" ReadMode (\handle -> do
        contents <- hGetContents handle
        putStr contents)

We also have the nice constructs readFile, writeFile, and appendFile.