DevTools

Cheatsheet Haskell

Linguagem funcional pura com tipos fortes e lazy evaluation

Back to languages
Haskell
70 cards found
Categories:
Versions:

Basic and Types


10 cards
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 * 2

The 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


10 cards
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


9 cards
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) xs

Recursion 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


9 cards
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


9 cards
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 String

The 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


8 cards
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
-- 354224848179261915075

The 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


8 cards
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 p

An 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


7 cards
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.Util

A 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 expression

Always 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.