Haskell Essentials
Essential Haskell patterns covering pure functions, type classes, monads, functors, and common idioms for functional programming.
Use Case
Use these patterns when you need to:
- Write pure functional code
- Understand Haskell's type system
- Work with monads and functors
- Handle side effects functionally
Basic Syntax
1-- Function definition
2square :: Int -> Int
3square x = x * x
4
5-- Pattern matching
6factorial :: Int -> Int
7factorial 0 = 1
8factorial n = n * factorial (n - 1)
9
10-- Guards
11abs' :: Int -> Int
12abs' n
13 | n < 0 = -n
14 | otherwise = n
15
16-- Let and where
17cylinder :: Double -> Double -> Double
18cylinder r h =
19 let sideArea = 2 * pi * r * h
20 topArea = pi * r^2
21 in sideArea + 2 * topArea
22
23-- Where clause
24bmiTell :: Double -> Double -> String
25bmiTell weight height
26 | bmi <= 18.5 = "Underweight"
27 | bmi <= 25.0 = "Normal"
28 | bmi <= 30.0 = "Overweight"
29 | otherwise = "Obese"
30 where bmi = weight / height^2
Lists and List Comprehensions
1-- List operations
2numbers = [1,2,3,4,5]
3head numbers -- 1
4tail numbers -- [2,3,4,5]
5init numbers -- [1,2,3,4]
6last numbers -- 5
7take 3 numbers -- [1,2,3]
8drop 2 numbers -- [3,4,5]
9
10-- List comprehension
11squares = [x^2 | x <- [1..10]]
12evens = [x | x <- [1..20], x `mod` 2 == 0]
13cartesian = [(x,y) | x <- [1,2,3], y <- [4,5,6]]
14
15-- Infinite lists (lazy evaluation)
16naturals = [1..]
17evens' = [2,4..]
18fibonacci = 0 : 1 : zipWith (+) fibonacci (tail fibonacci)
Higher-Order Functions
1-- map, filter, fold
2doubled = map (*2) [1,2,3,4,5]
3evens = filter even [1..10]
4sum' = foldl (+) 0 [1,2,3,4,5]
5product' = foldr (*) 1 [1,2,3,4,5]
6
7-- Function composition
8(.) :: (b -> c) -> (a -> b) -> (a -> c)
9f . g = \x -> f (g x)
10
11-- Example
12negateSum = negate . sum
13result = negateSum [1,2,3] -- -6
14
15-- $ operator (function application)
16sqrt $ 3 + 4 + 9 -- sqrt (3 + 4 + 9)
17sum $ map (*2) $ filter (>3) [1..10]
Type Classes
1-- Eq type class
2class Eq a where
3 (==) :: a -> a -> Bool
4 (/=) :: a -> a -> Bool
5 x /= y = not (x == y)
6
7-- Ord type class
8data TrafficLight = Red | Yellow | Green
9
10instance Eq TrafficLight where
11 Red == Red = True
12 Yellow == Yellow = True
13 Green == Green = True
14 _ == _ = False
15
16instance Ord TrafficLight where
17 Red `compare` _ = LT
18 _ `compare` Red = GT
19 Yellow `compare` Yellow = EQ
20 Yellow `compare` Green = LT
21 Green `compare` Yellow = GT
22 Green `compare` Green = EQ
23
24-- Show and Read
25instance Show TrafficLight where
26 show Red = "Red light"
27 show Yellow = "Yellow light"
28 show Green = "Green light"
Functors
1-- Functor type class
2class Functor f where
3 fmap :: (a -> b) -> f a -> f b
4
5-- List is a Functor
6fmap (*2) [1,2,3] -- [2,4,6]
7
8-- Maybe is a Functor
9fmap (*2) (Just 3) -- Just 6
10fmap (*2) Nothing -- Nothing
11
12-- Custom Functor
13data Box a = Box a deriving (Show)
14
15instance Functor Box where
16 fmap f (Box x) = Box (f x)
17
18-- Usage
19fmap (*2) (Box 3) -- Box 6
Applicative Functors
1-- Applicative type class
2class Functor f => Applicative f where
3 pure :: a -> f a
4 (<*>) :: f (a -> b) -> f a -> f b
5
6-- Maybe Applicative
7Just (*2) <*> Just 3 -- Just 6
8Just (*) <*> Just 3 <*> Just 5 -- Just 15
9
10-- List Applicative
11[(+1), (*2)] <*> [1,2,3] -- [2,3,4,2,4,6]
12
13-- Applicative style
14import Control.Applicative
15(+) <$> Just 3 <*> Just 5 -- Just 8
Monads
1-- Monad type class
2class Applicative m => Monad m where
3 return :: a -> m a
4 (>>=) :: m a -> (a -> m b) -> m b
5
6-- Maybe Monad
7safeDivide :: Double -> Double -> Maybe Double
8safeDivide _ 0 = Nothing
9safeDivide x y = Just (x / y)
10
11calculation = do
12 a <- safeDivide 10 2 -- Just 5
13 b <- safeDivide 20 4 -- Just 5
14 c <- safeDivide a b -- Just 1
15 return c
16
17-- Equivalent to:
18calculation' =
19 safeDivide 10 2 >>= \a ->
20 safeDivide 20 4 >>= \b ->
21 safeDivide a b
22
23-- List Monad
24pairs = do
25 x <- [1,2,3]
26 y <- [4,5,6]
27 return (x,y)
28-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
IO Monad
1-- Basic IO
2main :: IO ()
3main = do
4 putStrLn "What's your name?"
5 name <- getLine
6 putStrLn $ "Hello, " ++ name ++ "!"
7
8-- Reading files
9readFileExample :: IO ()
10readFileExample = do
11 contents <- readFile "input.txt"
12 putStrLn contents
13
14-- Writing files
15writeFileExample :: IO ()
16writeFileExample = do
17 writeFile "output.txt" "Hello, World!"
18
19-- Multiple actions
20processFile :: FilePath -> IO ()
21processFile path = do
22 contents <- readFile path
23 let processed = map toUpper contents
24 writeFile (path ++ ".processed") processed
25 putStrLn "File processed!"
Common Type Classes
1-- Semigroup
2class Semigroup a where
3 (<>) :: a -> a -> a
4
5-- Monoid
6class Semigroup a => Monoid a where
7 mempty :: a
8 mappend :: a -> a -> a
9 mconcat :: [a] -> a
10
11-- Examples
12[1,2,3] <> [4,5,6] -- [1,2,3,4,5,6]
13"Hello" <> " " <> "World" -- "Hello World"
14Sum 3 <> Sum 5 -- Sum 8
15Product 3 <> Product 5 -- Product 15
16
17-- Foldable
18sum' = foldr (+) 0
19product' = foldr (*) 1
20length' = foldr (\_ acc -> acc + 1) 0
21
22-- Traversable
23sequence' :: Monad m => [m a] -> m [a]
24traverse' :: Applicative f => (a -> f b) -> [a] -> f [b]
Algebraic Data Types
1-- Sum types (OR)
2data Bool' = True' | False'
3data Maybe' a = Nothing' | Just' a
4
5-- Product types (AND)
6data Point = Point Double Double
7data Person = Person { name :: String, age :: Int }
8
9-- Recursive types
10data List a = Empty | Cons a (List a)
11data Tree a = Leaf a | Node (Tree a) a (Tree a)
12
13-- Example tree operations
14treeMap :: (a -> b) -> Tree a -> Tree b
15treeMap f (Leaf x) = Leaf (f x)
16treeMap f (Node left x right) =
17 Node (treeMap f left) (f x) (treeMap f right)
18
19treeSum :: Num a => Tree a -> a
20treeSum (Leaf x) = x
21treeSum (Node left x right) = treeSum left + x + treeSum right
Common Patterns
Error Handling with Either
1data Either' a b = Left' a | Right' b
2
3divide :: Double -> Double -> Either String Double
4divide _ 0 = Left "Division by zero"
5divide x y = Right (x / y)
6
7-- Chaining with bind
8calculation = do
9 a <- divide 10 2
10 b <- divide 20 4
11 divide a b
State Monad
1import Control.Monad.State
2
3type Stack = [Int]
4
5pop :: State Stack Int
6pop = state $ \(x:xs) -> (x, xs)
7
8push :: Int -> State Stack ()
9push x = state $ \xs -> ((), x:xs)
10
11stackOps :: State Stack Int
12stackOps = do
13 push 3
14 push 5
15 a <- pop
16 b <- pop
17 return (a + b)
18
19-- Run: runState stackOps [] -- (8, [])
Reader Monad
1import Control.Monad.Reader
2
3type Config = String
4
5computation :: Reader Config String
6computation = do
7 config <- ask
8 return $ "Using config: " ++ config
9
10-- Run: runReader computation "my-config"
Notes
- Haskell is lazy - expressions evaluated only when needed
- Pure functions have no side effects
- IO monad isolates side effects
- Type inference is powerful - often don't need type signatures
- Pattern matching is exhaustive - compiler warns on missing cases
Gotchas/Warnings
- ⚠️ Lazy evaluation: Can cause space leaks if not careful
- ⚠️ Infinite lists: Work due to laziness, but be careful with strict operations
- ⚠️ Monad transformers: Stacking monads requires understanding transformers
- ⚠️ String performance: Use
TextorByteStringfor performance
comments powered by Disqus