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 Text or ByteString for performance
comments powered by Disqus