Haskell Programming Language Guide — Functional Programming and Type Theory
Haskell is a purely functional programming language with a strong static type system, lazy evaluation, and monadic effect handling — representing the most rigorous approach to writing correct-by-construction software.
What You’ll Learn
- Pure functions and referential transparency
- Lazy evaluation and its consequences
- Haskell’s type system and algebraic data types
- Monads: Maybe, IO, List, and State
- Pattern matching and recursion
- GHC, Cabal, and Stack tooling
Why It Matters
Haskell pushes the boundary of what a type system can guarantee at compile time — if your Haskell program compiles, it’s very likely correct. Durga Antivirus Pro uses Haskell for its rule-based static analysis engine where correctness is paramount. Financial institutions like Standard Chartered use Haskell for trading systems where a runtime error costs millions. Understanding Haskell transforms how you think about programming in any language — Rust’s Result type comes from Haskell’s Either monad, Python’s comprehensions come from Haskell’s list syntax.
Learning Path
flowchart LR
A[Pure Functions & Types<br/>You are here] --> B[Pattern Matching & Recursion]
B --> C[Higher-Order Functions]
C --> D[Monads & IO]
D --> E[Build a Real App]
Your First Haskell Program
-- hello.hs
main :: IO ()
main = putStrLn "Hello, Haskell!"
-- Run with: runhaskell hello.hsrunhaskell hello.hs
# Hello, Haskell!Pure Functions and Types
Haskell functions are pure — they always return the same output for the same input and have no side effects.
-- Type signature: functionName :: argType -> returnType
add :: Int -> Int -> Int
add x y = x + y
-- Polymorphic function
identity :: a -> a
identity x = x
-- Guards for conditional logic
describeAge :: Int -> String
describeAge age
| age < 13 = "Child"
| age < 20 = "Teenager"
| age < 65 = "Adult"
| otherwise = "Senior"
main :: IO ()
main = do
print (add 3 4) -- 7
print (identity "hello") -- "hello"
print (describeAge 25) -- "Adult"
print (describeAge 70) -- "Senior"7
"hello"
"Adult"
"Senior"Algebraic Data Types
Haskell’s type system lets you model domains precisely with algebraic data types (ADTs).
-- Product type (like a struct)
data Person = Person
{ name :: String
, age :: Int
} deriving (Show)
-- Sum type (enum on steroids)
data Color = Red | Green | Blue
deriving (Show, Eq)
-- Recursive sum type (linked list)
data MyList a = Empty | Cons a (MyList a)
deriving (Show)
-- Maybe is built-in: data Maybe a = Nothing | Just a
-- Either is built-in: data Either a b = Left a | Right b
describeColor :: Color -> String
describeColor Red = "Hot"
describeColor Green = "Nature"
describeColor Blue = "Ocean"
main :: IO ()
main = do
let alice = Person "Alice" 30
print alice -- Person {name = "Alice", age = 30}
print (describeColor Green) -- "Nature"
print (Cons 1 (Cons 2 (Cons 3 Empty))) -- Cons 1 (Cons 2 (Cons 3 Empty))Person {name = "Alice", age = 30}
"Nature"
Cons 1 (Cons 2 (Cons 3 Empty))Pattern Matching
Pattern matching destructures data and controls flow elegantly.
-- Match on list patterns
sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs
-- Match on Maybe
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)
-- Case expression
describeMaybe :: Maybe Int -> String
describeMaybe mx = case mx of
Nothing -> "No value"
Just n -> "Got: " ++ show n
main :: IO ()
main = do
print (sumList [1,2,3,4,5]) -- 15
print (safeDiv 10 2) -- Just 5
print (safeDiv 10 0) -- Nothing
print (describeMaybe (Just 42)) -- "Got: 42"15
Just 5
Nothing
"Got: 42"Lazy Evaluation
Haskell evaluates expressions only when needed, enabling infinite data structures.
-- Infinite list of natural numbers
naturals :: [Int]
naturals = [0..]
-- Infinite Fibonacci sequence
fibs :: [Int]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
-- Take only what you need
firstTenFibs :: [Int]
firstTenFibs = take 10 fibs
-- Lazy filtering (only evaluates as many as needed)
firstFiveEvens :: [Int]
firstFiveEvens = take 5 [x | x <- naturals, even x]
main :: IO ()
main = do
print firstTenFibs -- [0,1,1,2,3,5,8,13,21,34]
print firstFiveEvens -- [0,2,4,6,8]
-- Lazy I/O (process file lazily)
content <- readFile "example.txt"
let lines' = lines content
print (take 3 lines')[0,1,1,2,3,5,8,13,21,34]
[0,2,4,6,8]
["First line", "Second line", "Third line"]Monads
Monads structure effectful computations while keeping functions pure.
Maybe Monad (safe chaining)
import Data.Char (digitToInt)
-- Chain computations that might fail
safeSqrt :: Double -> Maybe Double
safeSqrt x
| x < 0 = Nothing
| otherwise = Just (sqrt x)
safeReciprocal :: Double -> Maybe Double
safeReciprocal 0 = Nothing
safeReciprocal x = Just (1 / x)
-- Do notation chains Maybes
compute :: Double -> Maybe Double
compute x = do
a <- safeSqrt x
b <- safeReciprocal a
return b
main :: IO ()
main = do
print (compute 16) -- Just 0.25
print (compute (-1)) -- Nothing
print (compute 0) -- NothingJust 0.25
Nothing
NothingIO Monad
main :: IO ()
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ "!")
-- File I/O
writeFile "output.txt" "Hello from Haskell!\n"
content <- readFile "output.txt"
putStrLn contentWhat is your name?
Alice
Hello, Alice!
Hello from Haskell!List Monad (non-determinism)
-- Generate all pairs (x, y) where x + y = 10
pairs :: [(Int, Int)]
pairs = do
x <- [1..9]
y <- [1..9]
guard (x + y == 10)
return (x, y)
import Control.Monad (guard)
main :: IO ()
main = print pairs[(1,9),(2,8),(3,7),(4,6),(5,5),(6,4),(7,3),(8,2),(9,1)]GHC, Cabal, and Stack
# GHC (Glasgow Haskell Compiler)
ghc --make hello.hs -o hello
./hello
# Cabal (Haskell build system)
cabal init # Create a new project
cabal build # Build the project
cabal run # Run the executable
cabal repl # Open GHCi with project context
# Stack (deterministic builds)
stack new my-project
stack build
stack exec my-projectCommon Mistakes
1. Forgetting the difference between = and <-
In do blocks, <- extracts a value from a monad. = binds a pure value. x <- getLine is correct; x = getLine gives you an IO action, not a String.
2. Infinite loops from lazy evaluation
let xs = [1..]
print xs -- hangs forever printing until memory overflowsAlways use take n to limit infinite structures.
3. Not handling all pattern match cases
head' [] = ??? -- missing case!
head' (x:_) = xGHC warns about incomplete patterns with -Wall. Use head' (x:_) = x with a separate case for [].
4. Confusing show with print
print x = putStrLn (show x). For Strings, print adds quotes. Use putStrLn for plain text output.
5. Mixing up $ and .
$ applies a function to an argument: f $ x = f x. . composes functions: (f . g) x = f (g x). map (show . (+1)) [1,2,3] vs map show $ [1,2,3].
6. Using return in pure code
return is not a control flow keyword — it wraps a value in a monad. return 5 :: Maybe Int = Just 5. Don’t use it outside do blocks.
Practice Questions
What is referential transparency? An expression is referentially transparent if it can be replaced with its value without changing the program’s behavior. Pure functions are referentially transparent; I/O operations are not.
What is lazy evaluation? Expressions are evaluated only when their value is needed. This enables infinite data structures, avoids unnecessary computation, but can make performance unpredictable.
What is a monad in Haskell? A monad is a type class with
>>=(bind) andreturnthat structures effectful computations. The Maybe monad chains computations that might fail; the IO monad chains operations with side effects.What is the difference between
dataandtype?datacreates a new algebraic data type.typecreates a type alias (like typedef).dataintroduces constructors;typeis just a synonym.What does
$do? The$operator is function application with low precedence and right associativity.f $ g $ h x=f (g (h x)). It eliminates parentheses.
Challenge: Implement a parser for simple arithmetic expressions (+, -, *, /) with proper operator precedence using algebraic data types and pattern matching.
Mini Project — Word Frequency Counter
import Data.List (sort, group)
import Data.Char (toLower, isAlpha)
import Control.Arrow ((&&&))
import Data.Ord (Down(..))
-- Clean a word: lowercase, keep only letters
cleanWord :: String -> String
cleanWord = map toLower . filter isAlpha
-- Count word frequencies
wordFreqs :: String -> [(Int, String)]
wordFreqs = sort
. map (length &&& head)
. group
. sort
. words
. unlines
. map cleanWord
. lines
-- Print top N words
printTop :: Int -> [(Int, String)] -> IO ()
printTop n freqs = do
putStrLn $ "Top " ++ show n ++ " words:"
putStrLn "-------------"
mapM_ printRow (take n sorted)
where
sorted = sort (map (\(c, w) -> (Down c, w)) freqs)
printRow (Down count, word) =
putStrLn $ printf "%-20s %d" word count
main :: IO ()
main = do
content <- readFile "input.txt"
let freqs = wordFreqs content
printTop 10 freqs
putStrLn $ "\nTotal unique words: " ++ show (length freqs)cat input.txt
# The quick brown fox jumps over the lazy dog.
# The fox jumps high and the dog runs fast.
runhaskell wordfreq.hs
# Top 10 words:
# -------------
# the 3
# fox 2
# jumps 2
# and 1
# brown 1
# dog 1
# fast 1
# high 1
# lazy 1
# over 1
# Total unique words: 11FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro