Getting started
Introduction
Haskell is a purely functional programming language with strong static typing, lazy evaluation, and powerful type inference.
Installation
# GHCup (recommended)
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
# Verify installation
ghc --version
ghci # Start REPL
Hello World
-- hello.hs
main :: IO ()
main = putStrLn "Hello, World!"
# Compile and run
ghc hello.hs
./hello
# Or run directly in GHCi
ghci hello.hs
> main
Quick Example
-- Function definition
square :: Int -> Int
square x = x * x
-- List operations
numbers = [1, 2, 3, 4, 5]
doubled = map (*2) numbers
-- [2, 4, 6, 8, 10]
-- Pattern matching
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
Basic Syntax
Function Definition
-- Type signature (optional but recommended)
add :: Int -> Int -> Int
add x y = x + y
-- Infix notation
3 `add` 4 -- 7
-- Operator definition
(+++) :: String -> String -> String
x +++ y = x ++ " " ++ y
-- Guards
absolute :: Int -> Int
absolute n
| n < 0 = -n
| otherwise = n
-- Where clause
bmiTell :: Double -> Double -> String
bmiTell weight height
| bmi <= 18.5 = "Underweight"
| bmi <= 25.0 = "Normal"
| otherwise = "Overweight"
where bmi = weight / height ^ 2
Let Expressions
-- Let in expression
cylinder :: Double -> Double -> Double
cylinder r h =
let sideArea = 2 * pi * r * h
topArea = pi * r ^ 2
in sideArea + 2 * topArea
-- Multiple bindings
let x = 5; y = 10 in x + y -- 15
-- Pattern matching in let
let (a, b) = (1, 2) in a + b -- 3
Comments
-- Single line comment
{- Multi-line
comment -}
{-# LANGUAGE OverloadedStrings #-} -- Language pragma
Basic Types
-- Primitive types
x :: Int -- Fixed precision integer
y :: Integer -- Arbitrary precision integer
z :: Float -- Single precision float
w :: Double -- Double precision float
b :: Bool -- True or False
c :: Char -- Unicode character
s :: String -- String is [Char]
-- Type inference
value = 42 -- Inferred as Num a => a
text = "hello" -- Inferred as String
Pattern Matching
Lists
-- Basic patterns
head' :: [a] -> a
head' [] = error "Empty list"
head' (x:_) = x
-- Multiple elements
sumFirst3 :: [Int] -> Int
sumFirst3 (a:b:c:_) = a + b + c
sumFirst3 _ = 0
-- As-patterns
firstLetter :: String -> String
firstLetter all@(x:xs) = "First letter of " ++ all ++ " is " ++ [x]
-- Wildcards
length' :: [a] -> Int
length' [] = 0
length' (_:xs) = 1 + length' xs
Tuples
-- Pair patterns
fst' :: (a, b) -> a
fst' (x, _) = x
snd' :: (a, b) -> b
snd' (_, y) = y
-- Triple patterns
addTriple :: (Int, Int, Int) -> Int
addTriple (x, y, z) = x + y + z
Case Expressions
-- Case statement
describe :: [a] -> String
describe xs = case xs of
[] -> "Empty list"
[x] -> "Singleton list"
_ -> "Longer list"
-- Pattern matching in case
processResult :: Either String Int -> String
processResult result = case result of
Left err -> "Error: " ++ err
Right val -> "Success: " ++ show val
Record Syntax
-- Data type with records
data Person = Person
{ firstName :: String
, lastName :: String
, age :: Int
}
-- Pattern matching records
getName :: Person -> String
getName Person{firstName = f, lastName = l} = f ++ " " ++ l
-- Shorthand
getAge :: Person -> Int
getAge Person{age = a} = a
Data Types
Type Aliases
-- Simple alias
type Name = String
type Age = Int
type PhoneNumber = String
-- Parameterized alias
type AssocList k v = [(k, v)]
type IntMap = AssocList Int
Algebraic Data Types
-- Sum types (OR)
data Bool = False | True
data Shape
= Circle Float
| Rectangle Float Float
| Triangle Float Float Float
-- Using sum types
area :: Shape -> Float
area (Circle r) = pi * r ^ 2
area (Rectangle w h) = w * h
area (Triangle a b c) =
let s = (a + b + c) / 2
in sqrt (s * (s - a) * (s - b) * (s - c))
Recursive Types
-- List definition
data List a = Empty | Cons a (List a)
-- Binary tree
data Tree a
= Leaf
| Node a (Tree a) (Tree a)
-- Example tree
exampleTree :: Tree Int
exampleTree = Node 5
(Node 3 Leaf Leaf)
(Node 7 Leaf Leaf)
Newtypes
-- Single-constructor wrapper
newtype Email = Email String
newtype UserId = UserId Int
-- Unwrapping
getEmail :: Email -> String
getEmail (Email e) = e
-- Type safety
userId :: UserId
userId = UserId 123
-- userId = 123 -- Type error!
Type Classes
Common Type Classes
-- Eq: Equality comparison
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
-- Ord: Ordering
class Eq a => Ord a where
compare :: a -> a -> Ordering
(<), (<=), (>), (>=) :: a -> a -> Bool
-- Show: String representation
class Show a where
show :: a -> String
-- Read: Parse from string
class Read a where
read :: String -> a
-- Enum: Sequential types
class Enum a where
succ, pred :: a -> a
toEnum :: Int -> a
fromEnum :: a -> Int
-- Num: Numeric types
class Num a where
(+), (-), (*) :: a -> a -> a
negate, abs, signum :: a -> a
Defining Instances
data TrafficLight = Red | Yellow | Green
-- Manual instance
instance Eq TrafficLight where
Red == Red = True
Yellow == Yellow = True
Green == Green = True
_ == _ = False
-- Derived instances
data Color = RGB Int Int Int
deriving (Eq, Ord, Show, Read)
-- Instance with constraints
instance (Eq a) => Eq (Maybe a) where
Nothing == Nothing = True
Just x == Just y = x == y
_ == _ = False
Functor
-- Definition
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- List instance
instance Functor [] where
fmap = map
-- Maybe instance
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
-- Usage
fmap (+1) [1, 2, 3] -- [2, 3, 4]
fmap (*2) (Just 5) -- Just 10
fmap show Nothing -- Nothing
-- Infix operator
(+1) <$> [1, 2, 3] -- [2, 3, 4]
Applicative
-- Definition
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
-- Maybe instance
instance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
_ <*> Nothing = Nothing
Just f <*> Just x = Just (f x)
-- Usage
pure (+) <*> Just 3 <*> Just 5 -- Just 8
pure (+3) <*> [1, 2, 3] -- [4, 5, 6]
-- Applicative style
(+) <$> Just 3 <*> Just 5 -- Just 8
Monad
-- Definition
class Applicative m => Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
-- Maybe instance
instance Monad Maybe where
return = Just
Nothing >>= f = Nothing
Just x >>= f = f x
-- Usage
Just 5 >>= \x -> Just (x + 1) -- Just 6
Nothing >>= \x -> Just (x + 1) -- Nothing
-- Do notation
addM :: Maybe Int -> Maybe Int -> Maybe Int
addM mx my = do
x <- mx
y <- my
return (x + y)
addM (Just 3) (Just 5) -- Just 8
addM (Just 3) Nothing -- Nothing
Lists
List Operations
-- Construction
xs = [1, 2, 3]
ys = 0 : xs -- [0, 1, 2, 3]
zs = [1, 2] ++ [3, 4] -- [1, 2, 3, 4]
-- Access
head [1, 2, 3] -- 1
tail [1, 2, 3] -- [2, 3]
last [1, 2, 3] -- 3
init [1, 2, 3] -- [1, 2]
xs !! 1 -- 2 (index)
-- Properties
length [1, 2, 3] -- 3
null [] -- True
null [1] -- False
-- Ranges
[1..5] -- [1, 2, 3, 4, 5]
[1,3..10] -- [1, 3, 5, 7, 9]
['a'..'e'] -- "abcde"
List Comprehensions
-- Basic
[x * 2 | x <- [1..5]]
-- [2, 4, 6, 8, 10]
-- With predicate
[x | x <- [1..10], even x]
-- [2, 4, 6, 8, 10]
-- Multiple generators
[(x, y) | x <- [1, 2], y <- ['a', 'b']]
-- [(1,'a'), (1,'b'), (2,'a'), (2,'b')]
-- Multiple predicates
[x | x <- [1..20], x `mod` 3 == 0, x `mod` 5 == 0]
-- [15]
-- Pattern matching
let xs = [(1, 2), (3, 4), (5, 6)]
[x + y | (x, y) <- xs]
-- [3, 7, 11]
Higher-Order Functions
-- map: Apply function to each element
map (+1) [1, 2, 3] -- [2, 3, 4]
map show [1, 2, 3] -- ["1", "2", "3"]
-- filter: Keep elements that match predicate
filter even [1..10] -- [2, 4, 6, 8, 10]
filter (>5) [1..10] -- [6, 7, 8, 9, 10]
-- foldl: Left fold
foldl (+) 0 [1, 2, 3] -- 6
foldl (-) 0 [1, 2, 3] -- -6
-- foldr: Right fold
foldr (+) 0 [1, 2, 3] -- 6
foldr (:) [] [1, 2, 3] -- [1, 2, 3]
-- zipWith: Combine two lists
zipWith (+) [1, 2, 3] [4, 5, 6] -- [5, 7, 9]
zipWith max [1, 5, 3] [2, 3, 6] -- [2, 5, 6]
-- takeWhile/dropWhile
takeWhile (<5) [1..10] -- [1, 2, 3, 4]
dropWhile (<5) [1..10] -- [5, 6, 7, 8, 9, 10]
Infinite Lists
-- Create infinite lists
naturals = [1..]
evens = [2, 4..]
ones = repeat 1
zeros = replicate 5 0 -- [0, 0, 0, 0, 0]
-- Cycle
cycle [1, 2, 3] -- [1, 2, 3, 1, 2, 3, ...]
-- Take from infinite lists
take 5 [1..] -- [1, 2, 3, 4, 5]
take 3 (cycle [1, 2]) -- [1, 2, 1]
-- Iterate
take 5 (iterate (*2) 1) -- [1, 2, 4, 8, 16]
Functions
Higher-Order Functions
-- Function as parameter
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)
applyTwice (+3) 10 -- 16
-- Function composition
(.) :: (b -> c) -> (a -> b) -> a -> c
(f . g) x = f (g x)
-- Examples
map ((*2) . (+1)) [1, 2, 3] -- [4, 6, 8]
(not . null) [1, 2] -- True
-- Dollar operator (low precedence)
($) :: (a -> b) -> a -> b
f $ x = f x
sum $ filter even [1..10] -- 30
-- Instead of: sum (filter even [1..10])
Lambda Functions
-- Anonymous functions
(\x -> x + 1)
(\x y -> x + y)
-- Usage
map (\x -> x * 2) [1, 2, 3] -- [2, 4, 6]
filter (\x -> x > 5) [1..10] -- [6, 7, 8, 9, 10]
-- Multiple parameters
zipWith (\x y -> x + y) [1, 2] [3, 4] -- [4, 6]
Currying
-- All functions are curried
add :: Int -> Int -> Int
add x y = x + y
-- Partial application
add5 :: Int -> Int
add5 = add 5
add5 3 -- 8
-- Sections (operators)
(+5) 3 -- 8
(5+) 3 -- 8
(/2) 10 -- 5.0
(2/) 10 -- 0.2
Point-Free Style
-- With explicit parameters
sum' xs = foldl (+) 0 xs
-- Point-free (no xs)
sum' = foldl (+) 0
-- More examples
isEven x = x `mod` 2 == 0
isEven = (==0) . (`mod` 2)
double xs = map (*2) xs
double = map (*2)
IO and Monads
Basic IO
-- Output
putStrLn :: String -> IO ()
putStr :: String -> IO ()
print :: Show a => a -> IO ()
main = do
putStrLn "Hello"
putStr "World"
print 42
-- Input
getLine :: IO String
getChar :: IO Char
main = do
putStrLn "What's your name?"
name <- getLine
putStrLn $ "Hello, " ++ name
Do Notation
-- Sequencing IO actions
main :: IO ()
main = do
putStrLn "First"
putStrLn "Second"
putStrLn "Third"
-- Binding results
main = do
line1 <- getLine
line2 <- getLine
let combined = line1 ++ line2
putStrLn combined
-- Return value
main = do
return () -- Returns (), type IO ()
File IO
import System.IO
-- Read entire file
readFile :: FilePath -> IO String
contents <- readFile "file.txt"
-- Write file
writeFile :: FilePath -> String -> IO ()
writeFile "output.txt" "Hello"
-- Append to file
appendFile :: FilePath -> String -> IO ()
appendFile "log.txt" "New entry\n"
-- Handle-based IO
main = do
handle <- openFile "file.txt" ReadMode
contents <- hGetContents handle
putStr contents
hClose handle
-- With bracket (safer)
import Control.Exception (bracket)
withFile' :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
withFile' path mode f = bracket
(openFile path mode)
hClose
f
Maybe Monad
-- Safe division
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)
-- Chaining operations
result = do
a <- safeDiv 10 2 -- Just 5
b <- safeDiv a 5 -- Just 1
c <- safeDiv b 0 -- Nothing
return (c + 1) -- Nothing
-- Alternative syntax
result = safeDiv 10 2 >>= \a ->
safeDiv a 5 >>= \b ->
safeDiv b 0 >>= \c ->
return (c + 1)
Common Patterns
Maybe
-- Definition
data Maybe a = Nothing | Just a
-- Usage
lookup :: Eq a => a -> [(a, b)] -> Maybe b
-- Pattern matching
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x
-- Functor instance
fmap (+1) (Just 5) -- Just 6
fmap (+1) Nothing -- Nothing
-- Applicative
pure (+) <*> Just 3 <*> Just 5 -- Just 8
-- Monad
Just 5 >>= \x -> Just (x * 2) -- Just 10
Either
-- Definition
data Either a b = Left a | Right b
-- Usage (Left for errors, Right for success)
safeDiv :: Double -> Double -> Either String Double
safeDiv _ 0 = Left "Division by zero"
safeDiv x y = Right (x / y)
-- Pattern matching
describe :: Either String Int -> String
describe (Left err) = "Error: " ++ err
describe (Right val) = "Value: " ++ show val
-- Chaining
result = do
a <- safeDiv 10 2 -- Right 5.0
b <- safeDiv a 0 -- Left "Division by zero"
c <- safeDiv b 2 -- Not executed
return c -- Left "Division by zero"
State Monad
import Control.Monad.State
-- Definition
type State s a = s -> (a, s)
-- Basic operations
get :: State s s
put :: s -> State s ()
modify :: (s -> s) -> State s ()
-- Example: Counter
type Counter = State Int
increment :: Counter ()
increment = modify (+1)
getCount :: Counter Int
getCount = get
-- Running state
runState :: State s a -> s -> (a, s)
evalState :: State s a -> s -> a
execState :: State s a -> s -> s
-- Usage
counter :: Counter Int
counter = do
increment
increment
getCount
runState counter 0 -- (2, 2)
evalState counter 0 -- 2
execState counter 0 -- 2
Reader Monad
import Control.Monad.Reader
-- Definition
type Reader r a = r -> a
-- Basic operations
ask :: Reader r r
local :: (r -> r) -> Reader r a -> Reader r a
-- Example: Configuration
data Config = Config
{ debugMode :: Bool
, maxRetries :: Int
}
type App = Reader Config
isDebug :: App Bool
isDebug = asks debugMode
withRetries :: App a -> App a
withRetries = local (\c -> c { maxRetries = 5 })
-- Running reader
runReader :: Reader r a -> r -> a
-- Usage
app :: App String
app = do
debug <- isDebug
retries <- asks maxRetries
return $ "Debug: " ++ show debug ++
", Retries: " ++ show retries
config = Config True 3
runReader app config -- "Debug: True, Retries: 3"
GHCi REPL
Basic Commands
-- Start GHCi
ghci
-- Load file
:load file.hs
:l file.hs
-- Reload
:reload
:r
-- Type information
:type expression
:t expression
:t map -- map :: (a -> b) -> [a] -> [b]
-- Kind information
:kind Type
:k Maybe -- Maybe :: * -> *
-- Info about identifier
:info identifier
:i Functor
Exploration
-- Show bindings
:show bindings
:show imports
-- Browse module
:browse Data.List
:browse Prelude
-- Search by type
:type-at file.hs line col
-- Documentation
:doc function
:doc map
Settings
-- Set options
:set +s -- Show timing
:set +t -- Show types
:set prompt "ghci> " -- Change prompt
-- Multi-line input
:set +m
:{
multiline
input
:}
-- Language extensions
:set -XOverloadedStrings
:set -XFlexibleInstances
Debugging
-- Trace execution
import Debug.Trace
trace :: String -> a -> a
traceShow :: Show a => a -> b -> b
-- Example
factorial n = trace ("n = " ++ show n) $
if n == 0 then 1 else n * factorial (n - 1)
-- Print during evaluation
factorial 3
-- Prints: n = 3, n = 2, n = 1, n = 0
-- Returns: 6
Tooling
Stack
# Initialize project
stack new myproject
cd myproject
# Build
stack build
# Run
stack exec myproject
# Test
stack test
# REPL
stack ghci
# Dependencies
# Edit stack.yaml and package.yaml
stack build
Cabal
# Initialize project
cabal init
# Build
cabal build
# Run
cabal run
# Test
cabal test
# REPL
cabal repl
# Install dependencies
# Edit myproject.cabal
cabal build
Project Structure
myproject/
├── app/
│ └── Main.hs # Executable entry point
├── src/
│ └── Lib.hs # Library code
├── test/
│ └── Spec.hs # Tests
├── package.yaml # Stack dependencies
├── stack.yaml # Stack config
├── myproject.cabal # Cabal config
└── README.md
Common Packages
# package.yaml (Stack)
dependencies:
- base >= 4.7 && < 5
- text # Efficient text
- bytestring # Byte strings
- containers # Maps, sets, trees
- vector # Efficient arrays
- aeson # JSON parsing
- mtl # Monad transformers
- transformers # Base transformers
- lens # Optics
- QuickCheck # Property testing
- hspec # Testing framework
Advanced Types
Polymorphism
-- Parametric polymorphism
id :: a -> a
id x = x
-- Constrained polymorphism
sort :: Ord a => [a] -> [a]
-- Multi-parameter type classes
class Convertible a b where
convert :: a -> b
instance Convertible Int String where
convert = show
GADTs
{-# LANGUAGE GADTs #-}
-- Generalized Algebraic Data Types
data Expr a where
Lit :: Int -> Expr Int
Bool :: Bool -> Expr Bool
Add :: Expr Int -> Expr Int -> Expr Int
If :: Expr Bool -> Expr a -> Expr a -> Expr a
eval :: Expr a -> a
eval (Lit n) = n
eval (Bool b) = b
eval (Add e1 e2) = eval e1 + eval e2
eval (If c t e) = if eval c then eval t else eval e
Type Families
{-# LANGUAGE TypeFamilies #-}
-- Associated types
class Collection c where
type Elem c
insert :: Elem c -> c -> c
empty :: c
instance Collection [a] where
type Elem [a] = a
insert = (:)
empty = []
-- Data families
data family Array a
data instance Array Int = IntArray [Int]
data instance Array Bool = BoolArray [Bool]
Existential Types
{-# LANGUAGE ExistentialQuantification #-}
-- Hide concrete type
data ShowBox = forall s. Show s => ShowBox s
instance Show ShowBox where
show (ShowBox s) = show s
-- Heterogeneous list
heteroList :: [ShowBox]
heteroList =
[ ShowBox 42
, ShowBox "hello"
, ShowBox True
]
-- Print all
mapM_ print heteroList
Lazy Evaluation
Basics
-- Expressions evaluated only when needed
x = 1 + 2 -- Not evaluated yet
y = x * 3 -- Still not evaluated
-- Forcing evaluation
z = y `seq` y -- Forces y (and x)
-- Infinite structures work
ones = 1 : ones
take 5 ones -- [1, 1, 1, 1, 1]
-- Only computes what's needed
head [1..] -- 1 (doesn't compute rest)
Strict Evaluation
-- Seq: Forces evaluation to WHNF
seq :: a -> b -> b
x `seq` y -- Evaluates x then returns y
-- Bang patterns
{-# LANGUAGE BangPatterns #-}
f !x = x + 1 -- x evaluated immediately
-- Strict data fields
data Pair = Pair !Int !Int
-- DeepSeq: Forces full evaluation
import Control.DeepSeq
deepseq :: NFData a => a -> b -> b
force :: NFData a => a -> a
Common Pitfalls
-- Space leaks from laziness
-- BAD: Accumulates thunks
sum' xs = foldl (+) 0 xs
-- GOOD: Strict fold
sum' xs = foldl' (+) 0 xs
-- Pattern matching forces evaluation
case x of
(a, b) -> ... -- Forces x to WHNF
-- Lazy patterns delay evaluation
case x of
~(a, b) -> ... -- Doesn't force x
Common Gotchas
Indentation
-- WRONG: Inconsistent indentation
main = do
putStrLn "Hello"
putStrLn "World" -- Parse error!
-- RIGHT: Aligned
main = do
putStrLn "Hello"
putStrLn "World"
-- RIGHT: Explicit braces
main = do { putStrLn "Hello"; putStrLn "World" }
String Types
-- String is [Char] - inefficient
type String = [Char]
-- Better: Use Text or ByteString
import Data.Text (Text)
import qualified Data.Text as T
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
-- Enable string literals
{-# LANGUAGE OverloadedStrings #-}
text :: Text
text = "Hello" -- No conversion needed
Numeric Types
-- Integer division
5 / 2 -- 2.5 (fractional)
5 `div` 2 -- 2 (integer division)
5 `mod` 2 -- 1 (modulo)
-- Type defaulting
x = 5 -- Num a => a
x + 3 -- Still polymorphic
print x -- Defaults to Integer
-- Explicit types
x :: Int
x = 5
Partial Functions
-- UNSAFE: Can throw exceptions
head [] -- Exception!
tail [] -- Exception!
maximum [] -- Exception!
-- SAFE: Use Maybe variants
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x
-- Or use fold/pattern matching
headOr :: a -> [a] -> a
headOr def [] = def
headOr _ (x:_) = x
Import/Export
-- Import everything
import Data.List
-- Selective import
import Data.List (sort, nub)
-- Hiding imports
import Data.List hiding (head, tail)
-- Qualified import
import qualified Data.Map as M
M.empty -- Data.Map.empty
-- Export list
module MyModule
( function1
, function2
, TypeName(..) -- Export type with all constructors
, TypeName2 -- Export type only
) where
Also see
- Haskell.org Official Documentation - Official language reference
- Learn You a Haskell for Great Good - Beginner-friendly tutorial
- Real World Haskell - Practical Haskell programming
- Hoogle - Search Haskell APIs by type signature
- Hackage - Haskell package repository
- GHC User's Guide - Compiler documentation
- Typeclassopedia - Guide to Haskell type classes
- Stack Documentation - Stack build tool guide