Getting Started
Basics & GHCi
Haskell is a purely functional language. GHCi is the interactive REPL. Function application needs no parentheses—succ 5 means succ(5) in other languages. Strings are just lists of characters.
-- GHCi interactive environment
-- launch: ghci (quit with :q)
-- simple arithmetic
> 2 + 3
5
-- function application (no parentheses needed)
> succ 5
6
-- define a function
> double x = x * 2
> double 10
20
-- lists
> [1,2,3,4]
[1,2,3,4]
-- strings are lists of chars
> "hello"
"hello"Comments
Haskell supports single-line comments with -- and multi-line comments with {- -}. Unlike most languages, Haskell comments can be nested, which is handy for commenting out code that already contains comments.
-- single line comment
{- multi-line comment
spanning multiple lines -}
{- nested {- comments -} are allowed -}
-- comments are ignored by the compilerHello World
main is the entry point of every Haskell program. putStrLn prints a string followed by a newline. Compile with ghc for a native executable, or run interpreted with runhaskell.
-- hello.hs
main :: IO ()
main = putStrLn "Hello, World!"
-- run interpreted
-- runhaskell hello.hs
-- or compile to an executable
-- ghc hello.hs && ./hellolet in GHCi
In GHCi, 'let' defines bindings without 'in'. In a source file, top-level definitions don't use 'let', and 'let' inside an expression requires a matching 'in'. This difference trips up many beginners.
-- in GHCi, 'let' defines a binding (no 'in' needed)
> let x = 5
> let double n = n * 2
> x + double x
15
-- in a source file, top-level bindings need no 'let'
y = 10
-- 'let' inside an expression needs 'in'
z = let a = 2 in a * a -- 4Type Inspection
:t (or :type) displays an expression's type; '::' reads as 'has type'. :i (info) shows typeclass instances and definitions. These GHCi commands are essential for exploring unfamiliar code.
-- :t shows the type of an expression
> :t 'a'
'a' :: Char
> :t True
True :: Bool
> :t "hi"
"hi" :: [Char]
> :t (+)
(+) :: Num a => a -> a -> a
-- :info shows typeclass info
> :info NumLoading Files
:load loads a module into GHCi; :reload reloads it after edits. :browse lists everything a module exports. :main runs the program with command-line arguments. These make the REPL workflow smooth.
-- GHCi commands
> :load MyModule.hs -- or :l
> :reload -- or :r, reload after edits
> :type main -- check a type
> :browse Data.List -- list a module's exports
> :main arg1 arg2 -- run main with args
-- :set +t auto-prints types of resultsTypes
Basic Types
Int is a fixed-size integer; Integer is unbounded and grows as needed. Double is preferred over Float for precision. Char holds a single Unicode character. Use type annotations to make types explicit.
-- Int: bounded integer (platform-dependent)
x :: Int
x = 9
-- Integer: arbitrary-precision (unbounded)
big :: Integer
big = 2 ^ 100
-- Float / Double (prefer Double)
piVal :: Double
piVal = 3.14159
-- Bool
flag :: Bool
flag = True
-- Char (single Unicode character)
c :: Char
c = 'a'Type Annotations
:: adds a type annotation to an expression or binding. Type signatures usually sit above the definition. They improve readability and let the compiler catch mistakes earlier.
-- explicit type signature above a binding
age :: Int
age = 30
-- annotate an expression inline with ::
result = (5 :: Int) + 3
-- function signature
add :: Int -> Int -> Int
add x y = x + y
-- annotate a list
nums :: [Double]
nums = [1.0, 2.0, 3.0]Type Variables
Lowercase identifiers in type signatures are type variables—they stand for any type, making functions polymorphic. The same variable must refer to the same type throughout one signature.
-- 'a' is a type variable (polymorphic)
id' :: a -> a
id' x = x
-- works for any type
> id' 5
5
> id' "hello"
"hello"
-- length works on any list
length :: [a] -> IntFunction Types
-> is right-associative: a -> b -> c means a -> (b -> c). The last type is the return type; the rest are parameters. Functions can take or return other functions.
-- '->' is right-associative
add :: Int -> Int -> Int
-- equivalent to: Int -> (Int -> Int)
-- takes a function as an argument
apply :: (a -> b) -> a -> b
apply f x = f x
-- returns a function
const' :: a -> b -> a
const' x y = xMaybe & Either
Maybe represents computations that may fail (Just value or Nothing). Either carries error info (Left error or Right value). Both replace null and exceptions for safer error handling.
-- Maybe: optional values
data Maybe a = Nothing | Just a
safeDiv :: Double -> Double -> Maybe Double
safeDiv _ 0 = Nothing
safeDiv x y = Just (x / y)
-- Either: a value or an error
data Either a b = Left a | Right b
-- convention: Left = error, Right = success
parse :: String -> Either String Int
parse s = Right (read s)Tuples & List Types
Tuples (a, b) hold a fixed number of values of possibly different types. Lists [a] hold any number of values of the same type. A tuple's type encodes its size; a list's type does not.
-- tuple: (a, b)
pair :: (Int, String)
pair = (1, "one")
-- list: [a]
nums :: [Int]
nums = [1, 2, 3]
-- list of tuples
pairs :: [(Int, String)]
pairs = [(1, "a"), (2, "b")]
-- empty list keeps the polymorphic type
empty :: [a]
empty = []Functions
Function Definition
Functions are defined by name, parameters, and '='. Application is a single space—no parentheses or commas. add 3 4 is really ((add 3) 4) due to currying.
-- name, parameters, '=', body
double :: Int -> Int
double x = x * 2
-- application: just a space, no parentheses
> double 5
10
-- multiple parameters (curried)
add :: Int -> Int -> Int
add x y = x + y
> add 3 4
7Function Application & $
Application binds tighter than operators and is left-associative. '$' is a low-precedence, right-associative application operator that replaces parentheses around the rightmost argument.
-- application is left-associative
> max 3 5
5
> max (max 3 5) 7
7
-- '$' has the lowest precedence, right-associative
> sum (map (*2) [1..5])
30
> sum $ map (*2) [1..5] -- same, fewer parens
-- chain reads left to right
> putStrLn $ show $ sum [1..10]
55Operators as Functions
Wrap an operator in parentheses to use it as a function: (+) 2 3. Sections partially apply an operator: (+10), (3/). Backticks turn a binary function infix: 10 `div` 3 reads more naturally.
-- wrap an operator in () to use as a function
> (+) 2 3
5
> (*) 4 5
20
-- sections: partial application of operators
> (+10) 5
15
> (3/) 12
0.25
> (/3) 12
4.0
-- backticks make a function infix
> 10 `div` 3
3
> 10 `mod` 3
1Function Composition
. composes functions from right to left: (f . g) x = f (g x). It enables point-free style, where arguments are omitted. Chaining reads inside-out but avoids nested parentheses.
-- (.) composes functions right to left
(.) :: (b -> c) -> (a -> b) -> a -> c
(f . g) x = f (g x)
> negate . sum $ [1,2,3]
-6
-- chaining
fn = ceiling . negate . tan
> fn (pi/4)
-1
-- point-free style
odd' = not . evenFunctions as Values
Functions are first-class: they can be passed as arguments, returned, and stored in data structures. applyTwice takes a function and applies it twice. This is the foundation of higher-order programming.
-- functions are first-class values
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)
> applyTwice (+3) 10
16
> applyTwice (\x -> x*x) 2
16
-- pass functions to higher-order functions
> map (*2) [1..5]
[2,4,6,8,10]
> filter even [1..6]
[2,4,6]Infix & Prefix
Any binary function can be written infix between backticks (3 `elem` xs), and any operator can be written prefix in parentheses (mod 10 3). Choose whichever reads more naturally in context.
-- infix operators can be written prefix with ()
> mod 10 3
1
> div 10 3
3
-- functions can be written infix with backticks
> 10 `mod` 3
1
> 10 `div` 3
3
-- elem: membership test, often infix
> 3 `elem` [1,2,3]
TrueLists
List Basics
Lists are homogeneous (all elements same type) singly-linked lists. Strings are just [Char]. ':' (cons) prepends an element in O(1). Lists are immutable—operations return new lists.
-- lists are homogeneous linked lists
nums :: [Int]
nums = [1, 2, 3, 4, 5]
-- strings are [Char]
chars = ['h','e','l','l','o']
str = "hello" -- same as chars
-- empty list
empty = []
-- cons (prepend) is O(1)
> 0 : [1,2,3]
[0,1,2,3]Range Lists
Ranges [a..b] generate lists. Specify the first two elements to set a step: [2,4..10]. Thanks to laziness, infinite lists like [1..] are valid—use take to extract a finite prefix.
-- ranges
> [1..5]
[1,2,3,4,5]
-- with a step (first two elements)
> [2,4..10]
[2,4,6,8,10]
-- decreasing needs the step
> [5,4..1]
[5,4,3,2,1]
-- infinite lists (lazy)
> take 5 [1..]
[1,2,3,4,5]
> take 3 (cycle [1,2])
[1,2,1]List Operations
head/tail/last/init access list ends—careful, they crash on empty lists (prefer pattern matching). length, null, and reverse are total. Most list access is O(n) since lists are linked.
-- basic access (partial: crash on empty)
> head [1,2,3]
1
> tail [1,2,3]
[2,3]
> last [1,2,3]
3
> init [1,2,3]
[1,2]
-- safe helpers
> length [1,2,3]
3
> null []
True
> reverse [1,2,3]
[3,2,1]List Concatenation
++ concatenates lists (O(n) on the left). : prepends one element in O(1). concat flattens one level of nesting. intercalate joins a list with a separator. Prefer : when building lists recursively.
-- '++' concatenates two lists
> [1,2] ++ [3,4]
[1,2,3,4]
-- ':' prepends a single element (O(1))
> 1 : [2,3]
[1,2,3]
-- concat flattens one level
> concat [[1,2],[3,4],[5]]
[1,2,3,4,5]
-- intercalate joins with a separator
> intercalate ", " ["a","b","c"]
"a, b, c"List Comprehensions
List comprehensions resemble set-builder notation: [output | generator, predicate, ...]. Multiple generators produce the Cartesian product. Predicates filter and let bindings define helpers.
-- like mathematical set-builder notation
> [x*2 | x <- [1..5]]
[2,4,6,8,10]
-- with a predicate (filter)
> [x | x <- [1..10], even x]
[2,4,6,8,10]
-- multiple generators = Cartesian product
> [(x,y) | x <- [1,2], y <- ['a','b']]
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
-- with a let binding
> [x*y | x <- [1..3], let y = x+1]
[2,6,12]Common List Functions
elem checks membership (often infix). sum and product reduce lists of numbers. take/drop extract or remove a prefix. splitAt splits at an index into a pair. All return new lists since lists are immutable.
-- membership (often written infix)
> elem 3 [1,2,3]
True
> 3 `elem` [1,2,3]
True
-- reductions
> sum [1..10]
55
> product [1..5]
120
-- take / drop a prefix
> take 3 [1..10]
[1,2,3]
> drop 3 [1..10]
[4,5,6,7,8,9,10]
-- split at an index
> splitAt 3 [1..6]
([1,2,3],[4,5,6])Tuples
Tuple Basics
Tuples group a fixed number of values of possibly different types. The number of components is part of the type—(Int,Int) and (Int,Int,Int) are distinct. Tuples are immutable.
-- tuples can hold different types
pair :: (Int, String)
pair = (1, "hello")
triple :: (Int, String, Double)
triple = (1, "x", 3.14)
-- the size is part of the type
-- (1,2) and (1,2,3) are different typesPair Functions
fst and snd extract the components of a 2-tuple (pair) only. swap exchanges the two components. For tuples of other sizes, use pattern matching. The type system prevents using fst on a triple.
-- fst/snd only work on pairs
> fst (1, "a")
1
> snd (1, "a")
"a"
-- swap exchanges the components
> swap (1, 2)
(2,1)
-- these do NOT work on triples
> fst (1, 2, 3) -- type error!Tuple Pattern Matching
Pattern matching destructures tuples by position. Use _ to ignore components you don't need. Tuple patterns work in function definitions, let bindings, list comprehensions, and case expressions.
-- destructure a tuple by position
describe :: (Int, String) -> String
describe (n, s) = show n ++ ": " ++ s
-- ignore components with _
firstOf :: (a, b, c) -> a
firstOf (x, _, _) = x
-- in a list comprehension
> [n | (n, _) <- [(1,'a'),(2,'b')]]
[1,2]zip & unzip
zip pairs elements from two lists, stopping at the shorter one. unzip is the inverse, splitting a list of pairs into a pair of lists. zipWith applies a function to paired elements instead of tupling.
-- zip pairs elements from two lists
> zip [1,2,3] ['a','b','c']
[(1,'a'),(2,'b'),(3,'c')]
-- stops at the shorter list
> zip [1,2,3] [4,5]
[(1,4),(2,5)]
-- unzip splits a list of pairs
> unzip [(1,'a'),(2,'b')]
([1,2],"ab")Tuples vs Lists
Use lists for variable-length sequences of the same type, and tuples for fixed-size heterogeneous groupings. A list's length isn't in its type; a tuple's arity is. Lists of tuples are common for tabular data.
-- list: same type, variable length
ints :: [Int]
ints = [1, 2, 3]
-- tuple: mixed types, fixed length
mixed :: (Int, String, Bool)
mixed = (1, "x", True)
-- list of tuples (tabular data)
people :: [(String, Int)]
people = [("Alice",30),("Bob",25)]
-- cannot mix types in a list
> [1, "two"] -- type errorPattern Matching
Basic Pattern Matching
Pattern matching tries branches top to bottom; the first match wins. Literal patterns match exact values. '_' is a wildcard matching anything. Always include a catch-all to avoid non-exhaustive match errors.
-- match on literals, top to bottom
lucky :: Int -> String
lucky 7 = "LUCKY SEVEN!"
lucky _ = "Sorry, you're out of luck"
> lucky 7
"LUCKY SEVEN!"
> lucky 13
"Sorry, you're out of luck"List Patterns
[] matches the empty list; (x:xs) binds x to the head and xs to the tail. (x:y:_) binds the first two elements. These patterns are fundamental for recursive list processing.
-- empty list
isEmpty [] = True
isEmpty _ = False
-- head and tail (cons pattern)
head' :: [a] -> a
head' (x:_) = x
-- first two elements
firstTwo :: [a] -> [a]
firstTwo (x:y:_) = [x,y]
firstTwo xs = xsTuple Patterns
Tuple patterns bind components by position. Use _ to ignore unneeded components. Nested tuples need nested patterns. This is the idiomatic way to unpack tuple values.
-- match by position
addPair :: (Int, Int) -> Int
addPair (x, y) = x + y
-- nested patterns
getAge :: (String, (Int, Int)) -> Int
getAge (_, (age, _)) = age
-- ignore with _
nameOf :: (String, Int) -> String
nameOf (name, _) = nameAs Patterns
As-patterns (name@pattern) bind a name to the whole matched value while also destructuring it. Useful when you need both the original and its parts, avoiding costly reconstruction.
-- '@' keeps the whole value AND its parts
firstLetter :: String -> String
firstLetter "" = "Empty string!"
firstLetter all@(x:_) =
"The first letter of " ++ all ++ " is " ++ [x]
> firstLetter "Haskell"
"The first letter of Haskell is H"Case Expressions
case performs pattern matching within an expression. It's useful when a function's body needs to branch on a value computed after some setup. Patterns are tried in order; add a catch-all if needed.
-- pattern match inside any expression
describe :: [a] -> String
describe xs =
case xs of
[] -> "empty"
[_] -> "singleton"
_ -> "longer"
-- case on any value, e.g. an Ordering
classify n = case compare n 0 of
LT -> "negative"
EQ -> "zero"
GT -> "positive"Constructor Patterns
Patterns can match data constructors like Red, Nothing, and Just. This is how algebraic data types are deconstructed. The compiler warns about non-exhaustive patterns, so cover all constructors.
-- match on data constructors
data Color = Red | Green | Blue
toString :: Color -> String
toString Red = "red"
toString Green = "green"
toString Blue = "blue"
-- match on Maybe
fromMaybe :: a -> Maybe a -> a
fromMaybe d Nothing = d
fromMaybe _ (Just x) = xGuards
Basic Guards
Guards test boolean conditions in order; the first True one wins. 'otherwise' is defined as True and serves as a catch-all. Guards are cleaner than if-then-else for multi-way branches.
-- '|' tests boolean conditions in order
bmiTell :: Double -> String
bmiTell bmi
| bmi <= 18.5 = "underweight"
| bmi <= 25.0 = "normal"
| bmi <= 30.0 = "overweight"
| otherwise = "obese"
> bmiTell 22.0
"normal"otherwise & Catch-all
otherwise is an alias for True, used as the final guard for completeness. Without a catch-all, a non-exhaustive guard causes a runtime error on unmatched inputs. Always end with otherwise unless every case is covered.
-- otherwise is just True
otherwise :: Bool
otherwise = True
-- a signum implementation
signum' :: Int -> Int
signum' n
| n < 0 = -1
| n == 0 = 0
| otherwise = 1Guards with where
Guards can use multiple parameters. A 'where' clause lets all guards share helper definitions—here bmi is computed once and reused by every guard. This keeps guards readable.
-- multiple parameters
max' :: Int -> Int -> Int
max' a b
| a > b = a
| otherwise = b
-- shared helpers via where
bmiTell :: Double -> Double -> String
bmiTell w h
| bmi <= 18.5 = "underweight"
| bmi <= 25.0 = "normal"
| otherwise = "overweight"
where bmi = w / h^2Guards vs Pattern Matching
Use pattern matching for fixed values and structure (literals, constructors, lists). Use guards for boolean conditions and ranges. They combine: match the structure first, then guard on conditions.
-- pattern matching: fixed values/structure
factorial 0 = 1
factorial n = n * factorial (n-1)
-- guards: ranges and conditions
clamp :: Int -> Int -> Int -> Int
clamp lo hi x
| x < lo = lo
| x > hi = hi
| otherwise = xif-then-else
In Haskell, 'if' is an expression that always returns a value—both branches are required. For multi-way branches, guards are cleaner than nested if-then-else. if-then-else suits simple two-way choices.
-- 'if' is an expression that returns a value
abs' :: Int -> Int
abs' n = if n < 0 then -n else n
-- nested ifs (prefer guards for readability)
grade :: Int -> Char
grade n =
if n >= 90 then 'A'
else if n >= 80 then 'B'
else if n >= 70 then 'C'
else 'F'Where / Let
where Clause
where attaches definitions to a function, scoped to its right-hand side (and guards). Definitions can refer to each other and appear in any order. Great for sharing helpers among guards.
-- where: definitions at the bottom, scoped to a function
area :: Double -> Double -> Double
area w h = w * h_adj
where
h_adj = h * 0.9 -- adjust height
scale = 0.9 -- a helper
-- definitions can refer to each other
f x = a + b
where a = x * 2
b = a + 1 -- refers to alet-in Expression
let ... in ... is an expression whose value is the 'in' part. Bindings are scoped to that expression only. Unlike where, let can appear anywhere an expression can.
-- let <bindings> in <expr>
cylinder :: Double -> Double -> Double
cylinder r h =
let side = 2 * pi * r * h
top = pi * r^2
in side + 2 * top
-- scoped to the expression only
> let z = 5 in z * z
25
-- z is not in scope herelet in do & Comprehensions
Inside do blocks and list comprehensions, let is a statement that needs no 'in'. The binding is in scope for subsequent lines or comprehension elements. Convenient for local computations.
-- in do, 'let' needs no 'in'
main = do
let x = 5
y = 10
putStrLn $ "sum = " ++ show (x + y)
-- in a list comprehension, no 'in'
> [x*y | x <- [1..3], let y = x+1]
[2, 6, 12]where with Patterns
where bindings support pattern matching on the right of '='. This is handy for unpacking values used across the function body. Keep where bindings focused on what the function needs.
-- where bindings support pattern matching
describe :: [a] -> String
describe xs = "head is " ++ show first
where (first:_) = xs
-- multiple where bindings
analyze :: [Int] -> String
analyze xs =
"sum=" ++ show s ++ ", len=" ++ show l
where s = sum xs
l = length xslet vs where
let is an expression (always returns a value); where is a syntactic declaration attached to a definition. Use let for local computation inline, and where for helpers shared across guards. Both are pure and lazy.
-- let: an expression, available wherever expressions go
-- (needs 'in', except in do/comprehension)
-- where: a declaration, attached to a function
-- (cannot be nested as an expression)
-- same result, two styles:
-- with let:
f x = let y = x+1 in y*y
-- with where:
f' x = y*y where y = x+1Recursion
Basic Recursion
Recursion replaces loops in Haskell. Define a base case (the stopping condition) and a recursive case that moves toward it. The compiler optimizes tail-recursive functions into loops.
-- base case + recursive case
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
> factorial 5
120List Recursion
Idiomatic list recursion matches [] (base) and (x:xs) (recursive), processing x and recurring on xs. Much list recursion is better expressed with fold or map, but the pattern is essential.
-- match [] then (x:xs)
length' :: [a] -> Int
length' [] = 0
length' (_:xs) = 1 + length' xs
sum' :: Num a => [a] -> a
sum' [] = 0
sum' (x:xs) = x + sum' xsAccumulators (Tail Recursion)
An accumulator carries the running result, making recursion tail-recursive—constant stack space. A common idiom uses a helper 'go' with the accumulator. GHC turns this into an efficient loop.
-- carry the running result in an accumulator
factorial :: Int -> Int
factorial n = go n 1
where
go 0 acc = acc
go k acc = go (k-1) (k*acc)
-- sum with an accumulator
sumTo :: Int -> Int
sumTo n = go n 0
where go 0 acc = acc
go k acc = go (k-1) (k+acc)Quick Sort
This famous example shows Haskell's expressiveness: list comprehensions split around a pivot, then recursively sort each part. Not the most efficient quicksort, but a beautiful demo of recursion and comprehensions.
-- elegant but not in-place quicksort
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (p:xs) =
qsort smaller ++ [p] ++ qsort larger
where
smaller = [x | x <- xs, x < p]
larger = [x | x <- xs, x >= p]
> qsort [3,1,4,1,5,9,2,6]
[1,1,2,3,4,5,6,9]Mutual Recursion
Mutual recursion is when two or more functions call each other. Here even' and odd' reduce toward the base case 0. Both must be defined (order doesn't matter in a module).
-- functions calling each other
even' :: Int -> Bool
even' 0 = True
even' n = odd' (n - 1)
odd' :: Int -> Bool
odd' 0 = False
odd' n = even' (n - 1)
> even' 4
TrueHigher-Order Functions
map
map :: (a -> b) -> [a] -> [b] transforms each element with a function, returning a new list of the same length. Combined with sections and lambdas, it expresses many loops concisely.
-- map applies a function to every element
> map (*2) [1,2,3]
[2,4,6]
> map (+1) [1..5]
[2,3,4,5,6]
-- with a lambda
> map (\x -> x*x) [1..4]
[1,4,9,16]
-- strings are lists of chars
> map toUpper "hello"
"HELLO"filter
filter :: (a -> Bool) -> [a] -> [a] keeps elements for which the predicate is True. Combining filter with length counts matching elements. It's the idiomatic way to select list elements.
-- keep elements satisfying a predicate
> filter even [1..10]
[2,4,6,8,10]
> filter (>3) [1,2,3,4,5]
[4,5]
-- with a lambda
> filter (\c -> c /= ' ') "hello world"
"helloworld"
-- count matches
> length (filter odd [1..100])
50foldl & foldr
foldl processes left-to-right; foldr right-to-left. Both reduce a list to a value using a binary function and a seed. foldr can handle infinite lists (if lazy); foldl cannot. Use foldl' (strict) for numeric sums to avoid space leaks.
-- foldl: left fold (function, seed, list)
> foldl (+) 0 [1..5]
15
-- foldr: right fold
> foldr (+) 0 [1..5]
15
-- associativity differs:
-- foldl: ((((0+1)+2)+3)+4)+5
-- foldr: 1+(2+(3+(4+(5+0))))
-- foldl1/foldr1 use the first element as seed
> foldl1 max [3,1,4,1,5]
5zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] applies a function to pairs from two lists, stopping at the shorter one. It's the generalization of zip. Useful for element-wise operations.
-- apply a function to paired elements
> zipWith (+) [1,2,3] [10,20,30]
[11,22,33]
> zipWith (*) [1,2,3,4] [2,2,2,2]
[2,4,6,8]
-- combine strings
> zipWith (\a b -> a ++ b) ["a","b"] ["1","2"]
["a1","b2"]
-- stops at the shorter list
> zipWith max [1,5,3] [2,4,6,8]
[2,5,6]scanl & scanr
scanl/scanr are like foldl/foldr but return the list of all intermediate accumulators. Useful for running totals, running maxima, and other cumulative computations.
-- scanl returns all intermediate accumulators
> scanl (+) 0 [1,2,3]
[0,1,3,6]
-- scanr from the right
> scanr (+) 0 [1,2,3]
[6,5,3,0]
-- running maximum
> scanl max 0 [3,1,4,1,5]
[0,3,3,4,4,5]takeWhile & dropWhile
takeWhile stops at the first element failing the predicate; dropWhile drops the prefix that satisfies it. span splits a list at the first failing element. Great for prefix-based processing.
-- takeWhile: take while the predicate holds
> takeWhile (<5) [1,2,3,4,5,4,3]
[1,2,3,4]
-- dropWhile: drop while the predicate holds
> dropWhile (<5) [1,2,3,4,5,4,3]
[5,4,3]
-- span splits at the first failing element
> span (<3) [1,2,3,4,5]
([1,2],[3,4,5])
-- first word of a string
> takeWhile (/= ' ') "hello world"
"hello"Lambda
Anonymous Functions
Lambdas are anonymous functions: \args -> body. The backslash resembles the Greek lambda. Use them for short, one-off functions passed to higher-order functions instead of naming them.
-- '\' defines a lambda (resembles the λ symbol)
> (\x -> x + 1) 5
6
-- used with map
> map (\x -> x * x) [1..4]
[1,4,9,16]
-- used with filter
> filter (\x -> x > 2) [1,2,3,4]
[3,4]Multi-parameter Lambda
A lambda's parameters are separated by spaces, mirroring normal function definitions: \x y -> .... Like all Haskell functions, lambdas are curried—one parameter at a time.
-- parameters separated by spaces
> (\x y -> x + y) 3 4
7
-- in zipWith
> zipWith (\x y -> x * y + 1) [1,2,3] [10,20,30]
[11,21,31]Lambda with Patterns
A lambda can pattern match its single argument, e.g. \(a,b) -> .... However, lambdas allow only one pattern with no fallback, so a non-matching input crashes. For multiple patterns, define a named function.
-- lambdas can pattern match their one argument
> map (\(a,b) -> a + b) [(1,2),(3,4)]
[3,7]
-- only ONE pattern, no fallthrough
-- this crashes if it doesn't match:
> (\(x:_) -> x) [] -- runtime errorLambdas in Higher-Order
Lambdas shine with fold, filter, and map. They can return other functions (\x -> \y -> ...), demonstrating currying. Keep them short—long lambdas are clearer as named functions.
-- combining with fold
> foldl (\acc x -> acc + x*2) 0 [1,2,3]
12
-- with filter (infix via backticks)
> length . filter (\x -> x `mod` 2 == 0) $ [1..10]
5
-- a lambda returning a function
> (\x -> \y -> x + y) 3 4
7Point-free Style
Point-free style omits explicit arguments, expressing functions via composition and application. It's concise but can hurt readability if overdone. Prefer it for short chains where the flow is obvious.
-- explicit: name every argument
sumSquares xs = sum (map (^2) xs)
-- point-free: omit arguments via composition
sumSquares' = sum . map (^2)
-- more examples
count p = length . filter p
oddNums = filter odd
negateAll = map negateCurrying
Partial Application
Every multi-argument function is really a chain of single-argument functions (currying). Applying fewer arguments than required yields a new function (partial application). This enables powerful reuse via function factories.
-- all functions are curried (one arg at a time)
add :: Int -> Int -> Int
add x y = x + y
-- apply fewer args to get a function
add5 :: Int -> Int
add5 = add 5
> add5 10
15
-- with map
> map (add 5) [1,2,3]
[6,7,8]Sections
Sections partially apply an infix operator: (+3), (3/), (>3). Note (-3) is the number negative three, not a section—use (subtract 3) for that. Sections are common with map and filter.
-- left section (fix the left operand)
> map (+3) [1,2,3]
[4,5,6]
-- right section (fix the right operand)
> map (3/) [9, 12, 18]
[0.333..,0.25,0.166..]
-- (-3) is the NUMBER, not a section!
> map (subtract 3) [4,5,6]
[1,2,3]
-- comparison sections
> filter (>3) [1..6]
[4,5,6]flip
flip takes a binary function and returns one with the first two arguments swapped. It's handy for partial application when you want to fix the second argument instead of the first.
-- flip swaps the first two arguments
flip :: (a -> b -> c) -> b -> a -> c
flip f x y = f y x
-- fix the second argument instead of the first
> map (flip (-) 1) [3,4,5]
[2,3,4]
-- divide from the other side
> map (flip div 2) [4,6,9]
[2,3,4]Currying Explained
a -> b -> c means a -> (b -> c): a function taking 'a' and returning a function. Application is left-associative: f x y = (f x) y. This is why partial application works naturally.
-- this signature:
max :: Int -> Int -> Int
-- is actually: Int -> (Int -> Int)
-- max takes an Int and returns a function
-- step by step
> :t max 5
max 5 :: Int -> Int
> (max 5) 10
10
-- application is left-associative
> max 5 10 == ((max 5) 10)
TrueFunctions Returning Functions
Functions can return other functions, naturally arising from currying. This enables function factories: multiplyBy 2 creates a doubler. Partial application like greet "Hi" configures a function for later use.
-- return a function
multiplyBy :: Int -> (Int -> Int)
multiplyBy n = \x -> n * x
-- or equivalently (currying)
multiplyBy' n x = n * x
> let double = multiplyBy 2
> double 21
42
-- configure a function via partial application
greet :: String -> String -> String
greet greeting name = greeting ++ ", " ++ name
sayHi = greet "Hi" -- a configured greeterTypeclasses
Basic Typeclasses
Typeclasses define shared interfaces. Eq provides == and /=; Ord provides <, >, compare; Show provides show; Read provides read. A type must implement a typeclass to use its methods.
-- Eq: equality
> 5 == 5
True
> "a" /= "b"
True
-- Ord: ordering
> 3 < 5
True
> compare 3 5
LT
-- Show: convert to a string
> show 5
"5"
> show [1,2,3]
"[1,2,3]"
-- Read: parse from a string
> read "5" + 2
7Type Constraints
'=>' introduces type constraints: the type on the left must satisfy them. Eq a => means 'a' must be in the Eq typeclass. Multiple constraints are parenthesized and comma-separated.
-- '=>' constrains the type variables
(==) :: Eq a => a -> a -> Bool
(<) :: Ord a => a -> a -> Bool
show :: Show a => a -> String
-- a function requiring Eq
elem :: Eq a => a -> [a] -> Bool
elem _ [] = False
elem x (y:ys) = x == y || elem x ys
-- multiple constraints
showMax :: (Ord a, Show a) => [a] -> String
showMax xs = show (maximum xs)Defining Typeclasses
class Name a where declares a typeclass with method signatures. Default implementations let instances define a minimal set—the others derive automatically. Here defining == gives /= for free, and vice versa.
-- declare a typeclass with method signatures
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
-- default implementations
x == y = not (x /= y)
x /= y = not (x == y)
-- minimal: define either (==) or (/=)Instance Declarations
instance Typeclass Type where implements a typeclass for a type. Each method must be defined unless a default exists. After this, TrafficLight values can use ==, /=, and show.
data TrafficLight = Red | Yellow | Green
instance Eq TrafficLight where
Red == Red = True
Yellow == Yellow = True
Green == Green = True
_ == _ = False
instance Show TrafficLight where
show Red = "Red light"
show Yellow = "Yellow light"
show Green = "Green light"Numeric Typeclasses
The numeric hierarchy includes Num (all numbers), Fractional (supports /), and Integral (supports div, mod). Int and Double are different types—use fromIntegral to convert between them safely.
-- Num: basic numeric operations
(+) :: Num a => a -> a -> a
(*) :: Num a => a -> a -> a
negate :: Num a => a -> a
-- Fractional: supports division
(/) :: Fractional a => a -> a -> a
-- Integral: integer division
div, mod :: Integral a => a -> a -> a
-- convert between numeric types
> fromIntegral (length [1,2,3]) + 0.5
3.5Deriving
Many typeclasses can be derived automatically: Eq, Ord, Show, Read, Enum, Bounded. This avoids writing boilerplate instances. Enum and Bounded enable range syntax ([Red ..]) and minBound/maxBound.
-- automatically derive common typeclasses
data Color = Red | Green | Blue
deriving (Eq, Ord, Show, Read, Enum, Bounded)
> show Red
"Red"
> Red < Green
True
> minBound :: Color
Red
> [Red ..]
[Red,Green,Blue]Algebraic Data Types
The data Keyword
data defines algebraic data types. Alternatives separated by '|' form a sum type; each is a value constructor. Constructors are functions that produce values of the type. Pattern matching deconstructs them.
-- sum type: alternatives separated by '|'
data Bool' = False' | True'
-- pattern match on constructors
not' :: Bool' -> Bool'
not' True' = False'
not' False' = True'
-- value constructors with no fields
data Weekday = Mon | Tue | Wed | Thu | FriConstructors with Fields
Constructors can carry fields. Point has two Double fields; Shape has two alternatives with different fields. Constructors are functions: Circle :: Double -> Shape. The type and constructor can share a name.
-- product type: constructors carry fields
data Point = Point Double Double
data Shape
= Circle Double -- radius
| Rect Double Double -- width, height
-- construct values
p = Point 1.0 2.0
c = Circle 5.0
r = Rect 3.0 4.0Parameterized Types
Type parameters make types generic. Maybe' a holds a value of any type 'a' or Nothing'. The parameter lets one definition serve Int, String, etc. Maybe is the standard way to model optional values.
-- a type parameter 'a' (like Maybe)
data Maybe' a = Nothing' | Just' a
-- a boxed value
data Box a = Box a
-- use it with any type
> Just' 5
Just' 5
> Box "hello"
Box "hello"
-- model failure with Maybe
divide :: Double -> Double -> Maybe' Double
divide _ 0 = Nothing'
divide x y = Just' (x/y)Recursive Types
A type can refer to itself, forming recursive structures. IntList is a singly-linked list of Ints. Tree a is a binary tree. Pattern matching naturally recurses over such structures.
-- a custom linked list
data IntList = INil | ICons Int IntList
-- build the list 1,2,3
xs = ICons 1 (ICons 2 (ICons 3 INil))
-- a binary tree
data Tree a = Leaf | Node a (Tree a) (Tree a)
tree = Node 1 (Node 2 Leaf Leaf) (Node 3 Leaf Leaf)A BST with Functions
Algebraic data types pair naturally with recursive functions via pattern matching. This BST implementation inserts while keeping order, and inorder traversal flattens to a sorted list. ADTs plus recursion replace classes in OOP.
data Tree a = Leaf | Node a (Tree a) (Tree a)
-- insert into a binary search tree
insert :: Ord a => a -> Tree a -> Tree a
insert x Leaf = Node x Leaf Leaf
insert x (Node y l r)
| x < y = Node y (insert x l) r
| otherwise = Node y l (insert x r)
-- inorder traversal yields a sorted list
inorder :: Tree a -> [a]
inorder Leaf = []
inorder (Node x l r) = inorder l ++ [x] ++ inorder rEnumerations
A sum type of nullary constructors acts like an enum. Deriving Enum enables succ/pred and range syntax ([North ..]); deriving Bounded gives minBound/maxBound. This is Haskell's idiomatic enumeration.
-- enum-like sum type
data Direction = North | East | South | West
deriving (Eq, Ord, Enum, Bounded, Show)
-- enumerate via Enum
> [North .. West]
[North,East,South,West]
-- Bounded: the limits
> minBound :: Direction
North
> maxBound :: Direction
West
-- cycle through directions
next :: Direction -> Direction
next West = North
next d = succ dRecords
Record Syntax
Record syntax names each field, improving readability over positional constructors. The type and constructor can share a name. Field order is independent—create values in any order.
-- without records (positional, error-prone)
data Person0 = Person0 String Int String
-- with records (named fields)
data Person = Person
{ name :: String
, age :: Int
, city :: String
}
-- construct with field names (any order)
p1 = Person { name = "Alice", age = 30, city = "NYC" }Accessing Fields
Each field name becomes an accessor function: name :: Person -> String. You can also pattern match with record syntax (Person { name = n }). Accessors are regular functions usable with composition.
-- field names are accessor functions
> name p1
"Alice"
> age p1
30
-- their types
> :t name
name :: Person -> String
> :t age
age :: Person -> Int
-- record patterns also work
greet :: Person -> String
greet (Person { name = n }) = "Hi, " ++ nUpdating Records
Record updates create a new value with specified fields changed, leaving others intact: record { field = newVal }. The original is immutable—updates return a copy. Multiple fields can change at once.
-- functional update: change only some fields
p2 = p1 { age = 31 }
-- the original is unchanged (immutable)
> age p1
30
> age p2
31
-- update multiple fields at once
p3 = p1 { age = 31, city = "LA" }Record Patterns
Record patterns bind fields by name, ignoring unspecified ones. Combined with as-patterns (p@Person{...}), you keep the whole record and select fields. This avoids fragile positional matching.
-- match and bind fields by name
birthday :: Person -> Person
birthday p@Person { age = a } = p { age = a + 1 }
-- ignore unneeded fields
getName :: Person -> String
getName Person { name = n } = n
-- as-pattern keeps the whole record
isAdult :: Person -> Bool
isAdult p@Person { age = a } = a >= 18Field Naming Caveats
Haskell's record field names live in the top-level namespace, so two records can't share a field name in the same module. Common workarounds are field prefixes (personName) or the DuplicateRecordFields extension.
-- field names share a top-level namespace
data Person = Person { name :: String }
data Company = Company { name :: String }
-- ERROR: 'name' clashes in the same module
-- workaround 1: prefix the fields
data Person' = Person' { personName :: String }
data Company' = Company' { companyName :: String }
-- workaround 2: language extensions
{-# LANGUAGE DuplicateRecordFields #-}Modules
Defining Modules
A module starts with 'module Name (exports) where'. The export list controls what's visible—if omitted, everything is exported. Unexported definitions act as private helpers.
-- file Geometry.hs
module Geometry
( sphereVolume -- explicit export list
, cubeVolume
) where
sphereVolume :: Float -> Float
sphereVolume r = (4/3) * pi * r^3
cubeVolume :: Float -> Float
cubeVolume s = s^3
-- a helper not in the export list is private
privateHelper = ...Importing Modules
import Module brings names into scope. Selective imports (import M (f, g)) limit what's imported. Qualified imports (import qualified M as M) require the M. prefix, avoiding name clashes.
-- import everything from Data.List
import Data.List
-- names are now in scope
> sort [3,1,2]
[1,2,3]
> nub [1,1,2,2,3]
[1,2,3]
-- import specific functions
import Data.Maybe (fromMaybe)
-- qualified: use the full module name
import qualified Data.Map as M
> M.emptySelective & Hiding Imports
Selective imports (import M (a, b)) bring only listed names. hiding imports everything except listed names. Use these to keep namespaces clean and avoid accidentally shadowing Prelude functions.
-- import only specific names
import Data.List (sort, nub, group)
-- import everything except some
import Data.List hiding (head, tail)
-- import a type without its constructors
import Data.Tree (Tree, Node)
-- import constructors too
import Data.Tree (Tree(..))Qualified Imports
Qualified imports force the module prefix, preventing clashes (e.g., Prelude's filter vs Data.Map's). import qualified M as M is idiomatic. Use qualified for modules with many overlapping names.
-- avoid name clashes with qualified
import qualified Data.Map as Map
import qualified Data.Set as Set
m = Map.empty
s = Set.fromList [1,2,3]
-- Prelude's filter vs Data.Map's
> filter even [1..6]
[2,4,6]
> Map.filter (>2) (Map.fromList [(1,'a'),(3,'b')])
fromList [(3,'b')]Prelude
Prelude is the default import, providing common functions and types. Hide entries to avoid clashes or to use better alternatives (e.g., foldl' from Data.List). Many projects use custom preludes.
-- Prelude is imported by default
-- provides: map, filter, length, (+), etc.
-- hide entries to avoid clashes
import Prelude hiding (head, tail, foldl)
-- use better alternatives
import Data.List (foldl') -- strict fold
-- common standard modules
import Data.List
import Data.Maybe
import Data.Char
import Data.EitherModule Hierarchy
Module names are hierarchical and mirror the directory structure: A.B.C lives in A/B/C.hs. A module can re-export another with 'module M' in its export list, useful for bundling a public API.
-- hierarchical names mirror directories
-- module Data.Geometry.Sphere where
-- lives in: Data/Geometry/Sphere.hs
-- import nested modules
import Data.Geometry.Sphere
import Data.Geometry.Cube
-- re-export a whole module
module MyMod
( module Data.List -- re-export
, myFunc
) where
import Data.List
myFunc = sortIO Monad
Basic IO
IO actions have type IO a—they describe effects performed when run (by main or GHCi). do sequences actions; '<-' extracts a value from an action. The IO type keeps side effects explicit and isolated.
-- IO actions produce effects when run
greet :: IO ()
greet = do
putStrLn "What's your name?"
name <- getLine
putStrLn ("Hello, " ++ name)
-- run in GHCi
> greet
What's your name?
Alice
Hello, AliceThe main Function
main :: IO () is the program entry point. do composes IO actions sequentially. getArgs provides command-line arguments. Pure code is called directly within do; results bind with '<-'.
-- every program starts in main
main :: IO ()
main = putStrLn "Hello, World!"
-- combine actions with do
main = do
greet
putStrLn "Done."
-- command-line arguments
import System.Environment
main = do
args <- getArgs
print argsdo Notation
do is syntactic sugar for the monadic bind (>>=) and (>>). Each 'x <- action' becomes action >>= \x -> .... let inside do needs no 'in'. do makes sequential IO read top-to-bottom.
-- do is sugar for sequential actions
echo = do
s <- getLine
putStrLn s
-- desugars to bind (>>=)
echo' = getLine >>= putStrLn
-- 'let' needs no 'in' inside do
greet = do
let msg = "Hi!"
putStrLn msgInput Functions
getLine reads a line (without newline), getChar a single character, getContents lazily reads all of stdin. Use '<-' to bind results. getContents is lazy—great for streaming pipelines.
-- read a line (without the newline)
> line <- getLine
hello world
> line
"hello world"
-- read a single character
> c <- getChar
a> c
'a'
-- read all of stdin lazily
process = do
text <- getContents
putStr (map toUpper text)Output Functions
putStrLn and putStr write a String to stdout, with/without a trailing newline. print equals putStrLn . show, so it adds quotes around strings. Choose based on newline needs and whether the value is a String.
-- with a trailing newline
putStrLn :: String -> IO ()
-- without a newline
putStr :: String -> IO ()
-- any Show-able value
print :: Show a => a -> IO ()
> putStrLn "hi" -- hi
> putStr "hi" -- hi (no newline)
> print 5 -- 5
> print "hi" -- "hi" (with quotes)File IO
readFile reads lazily, writeFile overwrites, appendFile adds to the end. Use bracket to ensure resources close on exceptions. Files are addressed by FilePath (an alias for String).
-- read a whole file (lazy)
readFile :: FilePath -> IO String
-- write a file (overwrites)
writeFile :: FilePath -> String -> IO ()
-- append to a file
appendFile :: FilePath -> String -> IO ()
main = do
contents <- readFile "input.txt"
writeFile "output.txt" (map toUpper contents)
-- safe resource handling
import Control.Exception (bracket)Functor / Applicative / Monad
Functor
A Functor is a context that can be mapped over. fmap (or <$> ) applies a function to the wrapped value, preserving the structure. Maybe, List, IO, and functions are all Functors.
-- Functor: a context you can map over
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- '<$>' is fmap as an operator
> fmap (+1) (Just 5)
Just 6
> (+1) <$> Just 5
Just 6
> (+1) <$> Nothing
Nothing
> map (+1) [1,2,3] -- map is fmap for lists
[2,3,4]Applicative
Applicative lifts multi-argument functions into a context. pure puts a value in, and <*> applies a wrapped function to a wrapped value. For lists, <*> yields the Cartesian product of applications.
-- Applicative: functions inside a context
class Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
-- apply a wrapped function
> pure (+) <*> Just 3 <*> Just 4
Just 7
> Just (+1) <*> Just 5
Just 6
-- for lists, <*> is the Cartesian product
> [(+1), (*2)] <*> [1,2,3]
[2,3,4,2,4,6]Monad & bind
A Monad supports chaining computations that depend on previous results. '>>=' (bind) feeds a wrapped value into a function returning a new wrapped value. return (or pure) injects a value. Monads unify sequencing, failure, and state.
-- Monad: sequential, dependent computations
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b -- bind
-- bind chains dependent computations
> Just 3 >>= \x -> Just (x+1)
Just 4
> Nothing >>= \x -> Just (x+1)
Nothing
-- '>>' ignores the left result
> putStrLn "a" >> putStrLn "b"
a
bdo Desugaring
do is sugar for (>>=) and let-bindings. Each 'x <- action' translates to action >>= \x -> .... Understanding the desugaring demystifies do and shows how monads enable it.
-- do notation
greet = do
name <- getLine
putStrLn ("Hi " ++ name)
-- desugars to bind
greet' = getLine >>= \name ->
putStrLn ("Hi " ++ name)
-- 'let' becomes a regular binding
compute = do
let x = 5
print (x * x)
-- desugars to:
compute' = let x = 5 in print (x*x)Maybe Monad Chain
Maybe's Monad short-circuits: once a step returns Nothing, the whole computation is Nothing without further work. do hides the manual Nothing-checking, making failure propagation automatic and clean.
-- short-circuit on Nothing
safeDiv :: Double -> Double -> Maybe Double
safeDiv _ 0 = Nothing
safeDiv x y = Just (x / y)
-- chain with do (auto-propagates failure)
calc = do
a <- safeDiv 10 2
b <- safeDiv a 2
return (b + 1)
> calc
Just 3.5
-- any Nothing stops the whole chain
> safeDiv 10 0 >>= \a -> safeDiv a 2
NothingList Monad
The list monad models non-determinism: each bind explores all possibilities, producing the Cartesian product. do over lists is equivalent to list comprehensions. This unifies comprehension and monadic styles.
-- list bind = non-deterministic computation
> [1,2] >>= \x -> [x, x*10]
[1,10,2,20]
-- do over lists == list comprehension
pairs = do
x <- [1,2]
y <- ['a','b']
return (x, y)
> pairs
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
-- equivalent comprehension
> [(x,y) | x <- [1,2], y <- ['a','b']]Type Aliases
type Alias
type creates a synonym—an alternate name for an existing type. It adds no runtime cost and provides no type safety (PhoneNumber and String are interchangeable). Use it to make signatures more readable.
-- 'type' creates a synonym (not a new type)
type String = [Char]
type PhoneNumber = String
type Name = String
-- improves readability of signatures
printEntry :: Name -> PhoneNumber -> IO ()
printEntry name num = putStrLn (name ++ ": " ++ num)newtype
newtype wraps an existing type in a single-field constructor, creating a distinct type at compile time with zero runtime overhead (it's erased). Unlike type, it prevents mixing Dollars with plain Double—real type safety.
-- newtype: one constructor, one field, zero cost
newtype Dollars = Dollars Double
newtype Count = Count Int
-- pattern match or unwrap the field
addDollars :: Dollars -> Dollars -> Dollars
addDollars (Dollars a) (Dollars b) = Dollars (a + b)
-- distinct type: can't mix with raw Double
total :: Dollars
total = addDollars (Dollars 10) (Dollars 20)type vs newtype vs data
type is a synonym (interchangeable, no safety). newtype is a distinct zero-cost wrapper (one constructor, one field). data is a full algebraic type (multiple constructors/fields, runtime cost). Pick newtype for type safety without overhead.
-- type: just a synonym (no new type, no safety)
type Score = Int
-- newtype: distinct type, zero cost, one field
newtype Score' = Score' Int
-- data: full ADT, runtime cost,
-- can have multiple constructors/fields
data Score'' = Score'' Int | Bonus Int
-- newtype is strict: exactly one
-- constructor and one field, but a distinct typenewtype for New Instances
newtype lets you attach different typeclass instances to the same underlying type. Sum and Product wrap the same number but have different Monoid instances (addition vs multiplication). A common, powerful pattern.
-- wrap to attach different typeclass instances
newtype Sum a = Sum { getSum :: a }
instance Num a => Monoid (Sum a) where
mempty = Sum 0
Sum a `mappend` Sum b = Sum (a + b)
newtype Product a = Product { getProduct :: a }
-- different Monoid: multiplication
-- same underlying type, different behavior
> getSum (Sum 3 <> Sum 4)
7
> getProduct (Product 3 <> Product 4)
12Parameterized Synonyms
Type synonyms can take parameters, acting as lightweight abstractions. AssocList k v is [(k,v)], making signatures self-documenting. Use them for clarity, but remember they're just aliases with no type safety.
-- type synonyms can take parameters
type AssocList k v = [(k, v)]
-- used in signatures (self-documenting)
lookup :: Eq k => k -> AssocList k v -> Maybe v
lookup _ [] = Nothing
lookup k ((k',v):rest)
| k == k' = Just v
| otherwise = lookup k rest
-- more examples
type Parser a = String -> Maybe (a, String)
type Matrix a = [[a]]Related Haskell snippets
Copy-paste ready code for common tasks.
Types and Type Classes
Define algebraic data types and type classes.
Maybe and IO Monads
Use Maybe for safety and IO for side effects.
List Comprehensions and Laziness
Generate lists with comprehensions and leverage laziness.
Functors, Applicatives, Monad Type Classes
The three core abstraction type classes.
IO and do Notation
Side-effectful programming in Haskell.
Modules and Imports
Organize code with modules and control exports.
Laziness and Strictness
Understand lazy evaluation and when to be strict.
Applicative Functors
Apply functions in a context with less power than Monad.
Was this helpful?