Cheatsheet Haskell
Linguagem funcional pura com tipos fortes e lazy evaluation
Haskell
Basic and Types
Basic Types
name :: String name = "Haskell" age :: Int age = 30 pi :: Double pi = 3.14159 active :: Bool active = True letter :: Char letter = 'A'
Haskell has static, strong typing. The main types are Int, Integer, Float, Double, Bool, Char and String.
Pattern Matching
-- In functions (by value): factorial :: Int -> Int factorial 0 = 1 factorial n = n * factorial (n - 1) -- On lists: myHead :: [a] -> a myHead (x:_) = x -- On tuples: first :: (a, b) -> a first (x, _) = x
Pattern matching deconstructs values directly in the arguments. The _ ignores unused parts and (x:xs) separates the head from the rest of the list.
Operators
-- Arithmetic: 2 + 3 -- 5 10 - 4 -- 6 3 * 7 -- 21 2 ^ 10 -- 1024 (power) 10 `div` 3 -- 3 (integer division) 10 `mod` 3 -- 1 (remainder) -- Comparison and logical: 5 == 5 -- True 5 /= 3 -- True (not equal) True && False -- False True || False -- True not True -- False
Common operators include + - * ^ and integer division div/mod. Comparison uses == and /=; logic uses &&, || and not.
Type Inference
-- With an explicit annotation: double :: Int -> Int double x = x * 2 -- Without annotation (inferred): triple x = x * 3 -- In GHCi, see the inferred type: -- :t triple -- triple :: Num a => a -> a
The compiler infers types automatically, but the explicit annotation (::) makes the code clearer and catches errors early.
Where
circle :: Double -> Double
circle r = area
where area = pi * r * r
-- Several local bindings:
stats xs = (mean, deviation)
where
mean = sum xs / len
len = fromIntegral (length xs)
deviation = sqrt (total / len)
total = sum (map (\x -> (x - mean)^2) xs)The where defines local bindings after the main expression. The definitions are visible in all guards of the same function.
Type Conversion
-- Int/Integer to fractional:
mean = fromIntegral (sum xs)
/ fromIntegral (length xs)
-- String to number:
n = read "42" :: Int
d = read "3.14" :: Double
-- Number to String:
s = show 42 -- "42"
t = show 3.14 -- "3.14"
-- Char and Int:
ord 'A' -- 65 (Data.Char)
chr 65 -- 'A'Haskell does not convert types implicitly. Use fromIntegral between numerics, read to parse a String and show for the opposite.
Defining Functions
add :: Int -> Int -> Int add a b = a + b -- Application by space (no parentheses): result = add 3 5 -- 8 -- Infix function with backticks: 8 `div` 3 -- 2 3 `mod` 2 -- 1
Functions take arguments separated by spaces. The -> arrow separates the types; the last one is the return type. Use backticks to call a function in infix form.
Let / in
-- let ... in is an expression:
volume :: Double -> Double
volume r =
let area = pi * r * r
in area * 10
-- let inside a list comprehension:
evens = [ y | x <- [1..10]
, let y = x * 2
, y > 5 ]
-- In GHCi (without in):
-- let double n = n * 2The let ... in is an expression that creates local bindings. Unlike where, it can be used anywhere, including inside comprehensions.
Guards
classify :: Int -> String classify n | n < 0 = "negative" | n == 0 = "zero" | otherwise = "positive" -- With a shared where: bmi weight height | bmi' < 18.5 = "under" | bmi' < 25 = "normal" | otherwise = "over" where bmi' = weight / height ^ 2
Guards (|) test conditions in sequence, like a readable if/else. The otherwise is the final case and is equivalent to True.
If the an Expression
-- if always needs an else: absolute :: Int -> Int absolute n = if n < 0 then -n else n -- Equivalent with guards: absolute' n | n < 0 = -n | otherwise = n -- case (explicit pattern matching): describe :: Bool -> String describe b = case b of True -> "yes" False -> "no"
In Haskell the if is an expression and always requires the else branch. The case ... of does explicit pattern matching on a value.
Lists
Creating Lists
xs = [1, 2, 3, 4, 5] -- Range: nums = [1..10] -- 1 to 10 evens = [2,4..20] -- step 2 letters = ['a'..'z'] -- alphabet -- Constructor (:) : list = 1 : 2 : 3 : [] -- [1,2,3] -- Empty list: empty = []
A list is homogeneous (all elements of the same type). The : (cons) operator prepends an element; [] is the empty list.
foldl and foldr
-- foldl: accumulates from the left foldl (+) 0 [1,2,3] -- 6 -- ((0+1)+2)+3 -- foldr: accumulates from the right foldr (+) 0 [1,2,3] -- 6 -- 1+(2+(3+0)) -- Strict versions (recommended): import Data.List (foldl') foldl' (+) 0 [1..1000000] -- Reimplement sum: total = foldl (+) 0
A fold reduces a list to one value, combining each element with an accumulator. The foldl' (strict) avoids accumulating thunks and is preferable for large computations.
Aggregation Functions
sum [1,2,3] -- 6 product [1,2,3,4] -- 24 maximum [3,1,4] -- 4 minimum [3,1,4] -- 1 -- and / or on lists of Bool: and [True, True] -- True or [False, True] -- True -- all / any with a predicate: all even [2,4,6] -- True any odd [2,4,5] -- True
These functions summarize lists: sum, product, maximum and minimum. The all and any test a predicate over all elements.
Basic Operations
xs = [1, 2, 3, 4, 5] head xs -- 1 tail xs -- [2,3,4,5] last xs -- 5 init xs -- [1,2,3,4] length xs -- 5 reverse xs -- [5,4,3,2,1] take 3 xs -- [1,2,3] drop 2 xs -- [3,4,5] xs !! 2 -- 3 (index) null xs -- False
These functions decompose lists. head/tail give the first element and the rest; take/drop take away N elements; !! accesses by index.
zip and zipWith
-- zip joins two lists into pairs: zip [1,2,3] ["a","b","c"] -- [(1,"a"),(2,"b"),(3,"c")] -- zipWith applies a function to the pairs: zipWith (+) [1,2,3] [10,20,30] -- [11,22,33] -- Stops at the shorter list: zip [1,2,3,4] ["x","y"] -- [(1,"x"),(2,"y")]
The zip combines two lists into a list of pairs. The zipWith does the same while applying a function to each pair. Both stop at the shorter list.
scan, replicate and cycle
-- scan: like fold, but keeps the steps: scanl (+) 0 [1,2,3] -- [0,1,3,6] -- replicate: repeats a value: replicate 4 7 -- [7,7,7,7] -- cycle: repeats the list (infinite): take 6 (cycle [1,2]) -- [1,2,1,2,1,2] -- repeat: repeats a value (infinite): take 3 (repeat 9) -- [9,9,9]
The scanl is like a fold that returns all intermediate values. The replicate creates finite lists; cycle and repeat create infinite lists.
List Comprehensions
-- Double of each element: [x*2 | x <- [1..5]] -- [2,4,6,8,10] -- With a filter (guard): [x | x <- [1..20], even x] -- evens -- Several generators: [(x,y) | x <- [1..3], y <- [1..3], x /= y] -- Strings are lists of Char: [toUpper c | c <- "hi"] -- "HI"
A list comprehension has the form [output | generator, filter]. The generator (<-) traverses the list and the filters (guards) select the elements.
Ranges and Infinite Lists
-- Finite range: [1..10] -- [1,2,...,10] [10,9..1] -- [10,9,...,1] -- Infinite range (lazy): allNums = [1..] -- infinite take 5 allNums -- [1,2,3,4,5] -- With a step: [1,3..20] -- [1,3,5,7,9,11,13,15,17,19] [0,0.5..3] -- [0.0,0.5,1.0,...,3.0]
Ranges use ... Thanks to lazy evaluation, you can define infinite lists like [1..] and consume only the needed part with take.
map and filter
-- map applies a function to each element: map (*2) [1,2,3] -- [2,4,6] map show [1,2,3] -- ["1","2","3"] -- filter keeps those that pass the test: filter even [1..10] -- [2,4,6,8,10] filter (>3) [1,2,3,4,5] -- [4,5] -- Equivalent with a comprehension: [x*2 | x <- [1,2,3]] -- [2,4,6]
The map transforms each element and the filter selects those satisfying the predicate. Both return a new list (they are immutable).
Strings the Lists
-- String is [Char]: s = "hello" length s -- 5 reverse s -- "olleh" map toUpper s -- "HELLO" -- Concatenate: "hello" ++ " " ++ "world" -- Build with concat: concat [["a","b"], ["c"]] -- "abc" -- Repeat: replicate 3 "ab" -- ["ab","ab","ab"]
A String is just a list of Char ([Char]), so all list functions work with text. The ++ concatenates two lists.
Functions
Composition (.)
-- (.) composes two functions: f = (*2) . (+1) f 3 -- 8 (double of (3+1)) -- Equivalent without composition: g x = (*2) ((+1) x) -- Several functions: h = sum . map (*2) . filter even h [1..10] -- 60
The . operator composes functions from right to left: (f . g) x = f (g x). It is ideal for building transformation pipelines.
Lambdas
-- Anonymous function with \: \x -> x * 2 \x y -> x + y -- Use with map/filter: map (\x -> x^2) [1,2,3] -- [1,4,9] filter (\x -> x > 3) [1..5] -- [4,5] -- Pattern matching in a lambda: \(x, y) -> x + y \(x:xs) -> x
A lambda (\args -> body) is an anonymous function. It is useful when the function is used only once, for example the an argument to map or filter.
flip, id, const, on
import Data.Function (on) -- flip swaps the arguments: flip (-) 5 3 -- -2 (3 - 5) -- id returns the argument: id 42 -- 42 -- const ignores the 2nd argument: const 1 99 -- 1 -- on applies a function after transforming: sortBy (compare `on` length) words
The flip swaps the argument order, id is the identity function and const always returns the first value. The on applies a function after transforming the arguments.
Application ($)
-- ($) applies with minimum precedence: sum $ map (*2) [1..10] -- same the: sum (map (*2) [1..10]) -- Avoids nested parentheses: show $ length $ filter even [1..20] -- Composition vs application: -- (.) combines functions -- ($) applies a function to an argument
The $ operator applies a function to the argument on the right, avoiding parentheses. It has the lowest precedence, so everything on the right is evaluated first.
Recursion
-- Fibonacci:
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
-- Quicksort:
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) =
qsort smaller ++ [x] ++ qsort larger
where
smaller = filter (<= x) xs
larger = filter (> x) xsRecursion is the natural form of repetition in Haskell (there are no for loops). You define a base case and a recursive case, often with pattern matching.
Currying
-- Every N-arg function is a chain: add :: Int -> Int -> Int add a b = a + b -- Partial application: add5 :: Int -> Int add5 = add 5 add5 3 -- 8 -- Real type: -- add :: Int -> (Int -> Int)
In Haskell, every multi-argument function is a chain of one-argument functions (currying). Applying only some arguments creates a new partial function.
Point-free Style
-- With an explicit argument: sumList xs = sum (map (*2) xs) -- Point-free (without xs): sumList = sum . map (*2) -- Another example: isEven x = even x isEven = even -- Not always more readable: -- f = (. (+1)) . (.) . (*)
The point-free style omits the arguments, writing the function the a composition of others. It is elegant for simple pipelines, but can hurt readability if overused.
Sections
-- Section: partial application of an operator: (+1) -- \x -> x + 1 (*2) -- \x -> x * 2 (10-) -- \x -> 10 - x -- Infix operator the a function: (`div` 2) -- \x -> x `div` 2 -- Practical use: map (+1) [1,2,3] -- [2,3,4] filter (>3) [1..6] -- [4,5,6]
A section is an operator with one side filled in, creating a function. (+1) is \x -> x + 1; order matters in non-commutative operators like -.
Higher-Order Functions
-- Function that takes a function: apply :: (Int -> Int) -> Int -> Int apply f x = f x apply (*2) 5 -- 10 -- Function that returns a function: multiplier :: Int -> (Int -> Int) multiplier n = \x -> x * n times3 = multiplier 3 times3 7 -- 21
Higher-order functions take or return functions. They are the foundation of map, filter and fold, allowing behaviors to be abstracted.
Data Types
Algebraic Data Types
-- Type with several constructors: data Shape = Circle Double | Rectangle Double Double area :: Shape -> Double area (Circle r) = pi * r * r area (Rectangle w h) = w * h -- Usage: area (Circle 2.0) -- 12.56 area (Rectangle 3.0 4.0) -- 12.0
A data type defines a type with one or more constructors separated by |. Each constructor can carry values, which are extracted by pattern matching.
newtype
-- newtype: wrapper of a single value: newtype Euros = Euros Double newtype Dollar = Dollar Double addEuros :: Euros -> Euros -> Euros addEuros (Euros a) (Euros b) = Euros (a + b) -- Do not mix Euros with Dollar! -- (type error at compile time)
The newtype creates a distinct type around a single value, with no runtime cost. It is ideal to avoid mixing up values with the same base type.
Recursive Types
-- Binary tree: data Tree a = Leaf | Node a (Tree a) (Tree a) -- Sum of all values: sumTree :: Num a => Tree a -> a sumTree Leaf = 0 sumTree (Node x l r) = x + sumTree l + sumTree r -- Usage: t = Node 1 (Node 2 Leaf Leaf) Leaf sumTree t -- 3
A type can refer to itself, creating recursive structures like trees. Pattern matching walks each branch down to the base case (Leaf).
Record Syntax
data Person = Person
{ name :: String
, age :: Int
, email :: String
}
-- Creation:
p = Person "Anna" 30 "ana@mail.com"
-- Automatic access (functions):
name p -- "Anna"
age p -- 30
-- Update (copy):
p2 = p { age = 31 }The record syntax (braces) automatically creates field accessor functions. Records are immutable: p { age = 31 } returns an updated copy.
Type Synonyms
-- Synonym (alias, no new type): type Name = String type Age = Int type Person = (Name, Age) greet :: Person -> String greet (name, age) = name ++ " is " ++ show age -- In Either signatures: type Result = Either String Int
A type synonym (type) gives an alternative name to an existing type, improving readability. It does not create a new type — it is just an alias.
Maybe
-- Maybe represents an optional value: -- data Maybe a = Nothing | Just a search :: [Int] -> Int -> Maybe Int search [] _ = Nothing search (x:xs) n | x == n = Just x | otherwise = search xs n -- Safe use: case search [1,2,3] 2 of Just x -> "found" Nothing -> "missing"
The Maybe models a value that may not exist: Just x or Nothing. It avoids null errors, forcing the absence to be handled.
Case Expressions
-- case does explicit pattern matching: day :: Int -> String day n = case n of 1 -> "Monday" 2 -> "Tuesday" _ -> "other" -- On Maybe: display :: Maybe Int -> String display m = case m of Just x -> "value: " ++ show x Nothing -> "empty"
The case ... of expression pattern matches on a value anywhere in the code. The _ is the default case that accepts anything.
Either
-- Either: two possible types: -- data Either a b = Left a | Right b divide :: Double -> Double -> Either String Double divide _ 0 = Left "Division by zero" divide a b = Right (a / b) -- Convention: Left = error, Right = success case divide 10 0 of Left msg -> putStrLn msg Right val -> print val
The Either represents a value that can be one of two types. By convention, Left carries the error and Right the correct result.
deriving
-- Derives instances automatically: data Color = Red | Green | Blue deriving (Show, Eq, Enum, Bounded) -- Show: allows printing show Red -- "Red" -- Eq: allows comparing Red == Green -- False -- Enum and Bounded: [Red .. Blue] -- the 3 colors minBound :: Color -- Red
The deriving automatically generates instances of common typeclasses. Show allows printing, Eq comparing, Enum enumerating and Bounded gives the limits.
Monads e IO
IO Monad
main :: IO ()
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ "!")
-- IO isolates side effects:
-- putStrLn :: String -> IO ()
-- getLine :: IO StringThe IO type isolates side effects (screen, files, network). The <- extracts the value of an IO action inside a do block.
Bind (>>=)
-- (>>=) passes the result to the next function: -- (>>=) :: m a -> (a -> m b) -> m b Just 3 >>= \x -> Just (x + 1) -- Just 4 -- Equivalent do: example = do x <- Just 3 Just (x + 1) -- (>>) ignores the previous result: putStrLn "a" >> putStrLn "b"
The >>= operator (bind) takes the value inside the monad and passes it to a function that returns a new monad. It is the mechanism do uses under the hood.
when, unless and forever
import Control.Monad (when, unless, forever) -- when: runs if True main = do n <- readLn :: IO Int when (n > 0) $ putStrLn "positive" -- unless: runs if False unless (even n) $ putStrLn "odd" -- forever: repeats forever forever $ putStrLn "loop"
These Control.Monad functions control the flow. The when runs the action if the condition is true, unless if it is false, and forever repeats indefinitely.
do Notation
-- do chains actions in sequence: main :: IO () main = do putStr "Name: " n <- getLine putStr "Age: " a <- getLine print (n, read a :: Int) -- let inside do (without in): let greeting = "Hello " ++ n putStrLn greeting
The do notation writes monadic code in a sequential, readable way. Each line is an action; the <- binds the result to a name and let creates pure bindings.
return and pure
-- return puts a value into the monad: return 5 :: Maybe Int -- Just 5 return 5 :: [Int] -- [5] return 5 :: Either e Int -- Right 5 -- pure (Applicative) does the same: pure 5 :: Maybe Int -- Just 5 -- return is NOT the C return! -- It does not exit the function, it only wraps.
The return (or pure) wraps a pure value in a monadic context. Unlike other languages, it does not end the function — it only creates the monadic value.
Maybe the a Monad
-- Chain with >>= (bind): result = Just 5 >>= \x -> Just (x * 2) >>= \y -> Just (y + 1) -- Just 11 -- With do notation: calculate :: Maybe Int calculate = do x <- Just 5 y <- Just 10 return (x + y) -- Just 15 -- If any is Nothing, everything is Nothing
The Maybe is a monad: it chains operations that may fail. If any step returns Nothing, the rest is skipped and the final result is Nothing.
sequence and mapM
-- sequence: list of monads -> monad of list sequence [Just 1, Just 2, Just 3] -- Just [1,2,3] sequence [Just 1, Nothing] -- Nothing -- mapM: map + sequence mapM read ["1","2","3"] :: Maybe [Int] -- Just [1,2,3] -- Versions that ignore results: sequence_ [putStrLn "a", putStrLn "b"] mapM_ putStrLn ["hello", "world"]
The sequence turns a list of monadic actions into an action that returns a list. The mapM combines map with sequence; the _ suffix discards the results.
Either the a Monad
type Result = Either String Int
parseAge :: String -> Result
parseAge s = case reads s of
[(n, "")] -> Right n
_ -> Left "Invalid age"
process :: String -> Result
process s = do
age <- parseAge s
if age >= 0
then Right (age * 2)
else Left "Negative age"The Either the a monad propagates errors: a Left stops the chain and is returned immediately. Only the Right values continue the processing.
List Monad
-- Lists are monads (non-determinism): [1,2,3] >>= \x -> [x, x*10] -- [1,10,2,20,3,30] -- do with lists = comprehension: pairs = do x <- [1..5] y <- [1..5] return (x, y) -- Equivalent: [(x,y) | x <- [1..5], y <- [1..5]]
The list is a monad that models non-deterministic computations. The do over lists is equivalent to a list comprehension, combining all possible values.
Advanced
Lazy Evaluation
-- Expressions are only evaluated when needed: costly = [ expensive x | x <- [1..1000000] ] -- Only computes the first 3: take 3 costly -- Natural short-circuit: -- (&&) does not evaluate the 2nd if the 1st is False False && expensiveCheck -- False
The lazy evaluation delays computing an expression until its value is actually needed. This allows infinite structures and avoids unnecessary work.
seq and Strictness
-- seq forces evaluation:
-- seq :: a -> b -> b
add x y = x `seq` x + y
-- BangPatterns (extension):
{-# LANGUAGE BangPatterns #-}
add' !x !y = x + y
-- foldl' is strict (recommended):
import Data.List (foldl')
foldl' (+) 0 [1..1000000]The seq forces the evaluation of a value before continuing, avoiding thunk buildup. The BangPatterns extension (!) marks arguments the strict.
Infinite Lists
-- Fibonacci with zipWith:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
take 10 fibs
-- [0,1,1,2,3,5,8,13,21,34]
-- Sieve of Eratosthenes (primes):
primes = sieve [2..]
where
sieve (p:xs) =
p : sieve [x | x <- xs, x `mod` p /= 0]
take 10 primes
-- [2,3,5,7,11,13,17,19,23,29]With lazy evaluation, infinite lists like fibs or primes are defined by self-reference. The take forces only the requested elements.
Modules and Imports
module MyModule
( publicFunction
, PublicType(..)
) where
import Data.List (sort, nub)
import qualified Data.Map the Map
import Data.Maybe (fromMaybe, isJust)
-- qualified requires a prefix:
Map.fromList [("a", 1)]A module controls what is exported. A selective import brings only specific names; the qualified forces the use of a prefix (e.g. Map.) to avoid clashes.
Memoization
-- Memoized Fibonacci (infinite list):
fib :: Int -> Integer
fib = (fibs !!)
where
fibs = 0 : 1 :
zipWith (+) fibs (tail fibs)
fib 100 -- instant
-- 354224848179261915075The memoization stores results already computed. Defining fibs the a shared infinite list makes each value be computed only once, making fib 100 immediate.
Foldable and Traversable
import qualified Data.Map the Map
import Data.Foldable (toList)
-- Foldable: reduces structures to a value
m = Map.fromList [("a",1),("b",2)]
sum m -- 3 (sums the values)
toList m -- [1,2]
-- Traversable: map with effects
traverse print [1,2,3] :: IO [()]
-- foldMap combines with a Monoid:
foldMap show [1,2,3] -- "123"Foldable generalizes fold to any structure (lists, maps, trees). Traversable allows mapping with effects, such the IO or Maybe, preserving the shape.
Data.Map and Data.Set
import qualified Data.Map the Map
import qualified Data.Set the Set
-- Map (dictionary):
m = Map.fromList [("a", 1), ("b", 2)]
Map.lookup "a" m -- Just 1
Map.insert "c" 3 m
-- Set:
s = Set.fromList [1,2,2,3] -- {1,2,3}
Set.member 2 s -- True
Set.union s (Set.fromList [4])The Data.Map is a key-value dictionary and Data.Set a set without duplicates. Import them with qualified to avoid name clashes with the Prelude.
Exceptions
import Control.Exception (try, catch, SomeException)
-- try returns Either:
main = do
r <- try (evaluate (1 `div` 0))
:: IO (Either SomeException Int)
case r of
Left ex -> putStrLn ("Error: " ++ show ex)
Right v -> print v
-- catch with handler:
catch (print (read "abc" :: Int))
(\(e :: SomeException) -> putStrLn "failed")Exceptions in Control.Exception handle errors in IO. The try converts the exception into an Either; the catch runs a handler when an error occurs.
Typeclasses
Defining a Typeclass
-- A typeclass declares operations: class Describable a where describe :: a -> String -- With a default method: class Greeting a where greet :: a -> String greet _ = "Hello!" -- default
A typeclass declares a set of functions that several types can implement. It is similar to an interface, but based on types — it defines behavior, not data.
Read and Enum
-- Read: parse a String (inverse of Show) read "42" :: Int -- 42 read "3.14" :: Double -- 3.14 read "True" :: Bool -- True -- Enum: enumerable types ['a'..'e'] -- "abcde" fromEnum 'A' -- 65 toEnum 65 :: Char -- 'A' -- succ and pred: succ 5 -- 6 pred 5 -- 4
The Read parses a String into a value (requires a type annotation). The Enum enables sequences with .. and the succ/pred functions.
Instantiating a Typeclass
data Person = Person String Int
instance Describable Person where
describe (Person n a) =
n ++ " (" ++ show a ++ ")"
-- Several types, same interface:
data Product = Product String Double
instance Describable Product where
describe (Product n p) = n ++ ": " ++ show pAn instance implements the typeclass for a concrete type. Each type gives its own definition of the methods declared in the class.
Functor
-- Functor: maps inside a context class Functor f where fmap :: (a -> b) -> f a -> f b -- On lists (fmap = map): fmap (*2) [1,2,3] -- [2,4,6] -- On Maybe: fmap (*2) (Just 5) -- Just 10 fmap (*2) Nothing -- Nothing -- The <$> operator is a synonym for fmap: (*2) <$> Just 5 -- Just 10
A Functor is a container over which a function can be mapped with fmap. It applies the function to the value inside the context (list, Maybe, etc.) without changing it.
Constraints
-- Simple constraint: bigger :: Ord a => a -> a -> a bigger x y = if x > y then x else y -- Several constraints: showPair :: (Show a, Show b) => (a, b) -> String showPair (x, y) = show x ++ ", " ++ show y -- Class context: class Eq a => Orderable a where cmp :: a -> a -> Ordering
A constraint (Ord a =>) restricts the types accepted by a function. It requires the type to belong to a certain typeclass in order to use its operations.
Applicative
-- Applicative: functions inside the context -- pure puts a value into the context pure 5 :: Maybe Int -- Just 5 -- <*> applies a wrapped function: Just (+) <*> Just 3 <*> Just 4 -- Just 7 -- Combine with <$> : (+) <$> Just 3 <*> Just 4 -- Just 7 -- On lists: (*) <$> [1,2] <*> [10,20] -- [10,20,20,40]
An Applicative extends the Functor: it allows applying functions that are also inside a context, using pure and the <*> operator.
Show, Eq and Ord
-- Show: convert to String show 42 -- "42" show True -- "True" -- Eq: equality (==, /=) 5 == 5 -- True -- Ord: ordering (<, >, compare) compare 3 5 -- LT compare 5 5 -- EQ compare 7 5 -- GT -- sort needs Ord: import Data.List (sort) sort [3,1,2] -- [1,2,3]
Show converts values to String, Eq enables == and /=, and Ord adds ordering (<, >, compare).
Typeclass Monad
-- Monad: chains operations with context class Applicative m => Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a -- Example with Maybe: Just 5 >>= \x -> Just (x * 2) -- Just 10 -- return is the same the pure: return 5 :: Maybe Int -- Just 5
A Monad allows chaining operations that return values with context, through bind (>>=). The return puts a pure value into the monadic context.
Tools and Good Practices
GHC and Compilation
# Compile an executable: ghc -o program Main.hs # Compile with optimization: ghc -O2 -o program Main.hs # Generate only objects: ghc --make Main.hs # Check types without running: ghc -fno-code Main.hs
The GHC is the main Haskell compiler. The --make compiles the module and its dependencies; the -O2 flag enables performance optimizations.
Haddock (Documentation)
-- | Function description (Haddock): double :: Int -> Int double x = x * 2 -- ^ argument -- * Section -- >>> double 3 (doctest example) -- 6 # Generate documentation: cabal haddock stack haddock
The Haddock generates HTML documentation from special comments. The -- | documents the following definition and >>> creates testable examples with doctest.
GHCi (REPL)
# Start the REPL: ghci > :l file.hs -- load module > :r -- reload > :t (+) -- see type > :i Int -- type info > :set +t -- show types > :browse Data.List -- list functions
The GHCi is the interactive REPL. The :t commands show the type, :i gives detailed information and :l/:r load and reload modules.
Project Structure
my-project/
app/
Main.hs -- executable
src/
MyModule.hs -- library
test/
Spec.hs -- tests
my-project.cabal
stack.yaml
-- Module = file:
-- src/Data/Util.hs -> module Data.UtilA typical project separates app/ (executable), src/ (library) and test/. The file path defines the module name: src/Data/Util.hs is module Data.Util.
Cabal
# Create project: cabal init # The .cabal file defines the project: # name, version, build-depends... # Main commands: cabal build -- compile cabal run -- run cabal test -- tests cabal repl -- project GHCi cabal update -- update index
The Cabal manages projects and dependencies. The .cabal file describes the package and its dependencies in build-depends; cabal build compiles everything.
Best Practices and Debugging
-- Always write type signatures:
double :: Int -> Int
-- Use undefined the a placeholder:
complexFunction :: a -> b
complexFunction = undefined
-- Debug with trace (Debug.Trace):
import Debug.Trace (trace)
f x = trace ("x = " ++ show x) (x * 2)
-- Quick tests in GHCi:
-- :t expressionAlways sign the types, use undefined the a skeleton and rely on trace from Debug.Trace to print values while debugging without changing the function type.
Stack
# Create project from a template: stack new my-project # Main commands: stack build -- compile stack run -- run stack test -- tests stack ghci -- REPL stack exec -- program -- run binary
The Stack is an alternative to Cabal with reproducible GHC versions, defined in stack.yaml. It guarantees that all developers use the same toolchain.