Haskell Guide
Haskell is the most commonly used pure functional language.
Keep in mind that Haskell is a very different kind of programming language. All terms like instance, class and return have different meaning in Haskell context.
All examples in this note are written so they can be ran as separate programs using a Haskell compiler e.g. compile online - haskell.
Glasgow Haskell Compiler (GHC) is the leading implementation of Haskell. You can download it from www.haskell.org/platform.
Coding Style
Maximum line length is 80 characters.
Indention is made using spaces. Prefer 4 space indention but focus in showing the logical structure of the code. Tabs are illegal.
Use one blank line between top-level definitions. No more, no less.
Type and data constructors are in pascal case. Everything else in camel case.
Int
Maybe
functionNamesAreLowercase
connectDatabase
argumentsAreLowercase
x
bird
listsHaveSuffixs
xs
birds
Underscore _
as a name means that the value assigned to it won't be used.
Names ending with a apostrophe '
means an alternative version. This usually means the function or variable is lazily evaluated, but not always. Good to know but you should never name anything like this.
doubleSmall x = if x < 10
then x * 2
else x
doubleSmall' x = (if x < 10 then x * 2 else x) + 1
main = do
print (doubleSmall 9) -- 18
print (doubleSmall' 9) -- 19
General
Code consists of Haskell scripts ending in .hs
suffix.
-- test.hs
double x = x + x
# ghci test.hs
> double 2
4
# use :reload command in ghci after changing the file.
> :reload
Entry point of a Haskell program is the main
function.
main = print "Hello world!" -- "Hello world!"
Definitions are made with =
. If you specify parameters, it's a function definition. If you don't specify any parameters, it's a name definition.
doubleMe x = x * 2
personAge = 6
main = print (doubleMe personAge) -- 12
Function applications is usually by prefix or inflix syntax. All prefix functions can be used as a inflix function by adding backticks.
main = do
print (div 92 10) -- 9 (prefix call)
print (1 + 1) -- 2 (inflix call)
print (92 `div` 10) -- 9 (prefix as inflix call)
$
makes the previous function application right-associative. Basically, it takes whatever is on the right side and applies it to the left side. It's used to clean up the code or to apply a series of functions on a single value.
main = do
print (map (+1) [1,2,3]) -- [2,3,4]
print $ map (+1) [1,2,3] -- [2,3,4]
print (map ($ 3) [(+ 4), (* 10), (^ 2)]) -- [7,30,9]
.
combines functions. It means function composition. The functions are applied from right to left. It's used to clean up the code.
main = do
print (map (negate . abs) [1,0,-3,11,-20]) -- [-1,0,-3,-11,-20]
\
creates a lambda. Lambdas are anonymous functions that are defined and used only in one place.
main = do
print (map (\(a,b) -> a + b) [(1,2), (3,4), (1,1)]) -- [3,7,2]
Functions are curried. Function that seems to take several parameters actually takes just one parameter and returns a function that takes the next parameter and so on. Every ->
identifies a single function that takes what is on the left side and returns what is on the right side.
-- Function with this type:
a -> b -> c
-- In fact just returns a function with this type:
b -> c
-- Which returns:
c
-- `max` definition.
-- max :: (Ord a) => a -> a -> a
-- These two following lines are equivalent because of currying.
main = do
print (max 4 5) -- 5
print ((max 4) 5) -- 5
Function as parameters are defined by (a -> a).
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)
main = print (applyTwice (+3) 10) -- 16
Architecture
You include other Haskell modules with the import
keyword.
-- Import `nub` function from `Data.List` module.
-- You can import everything from the module with `import Data.List`
-- You can import except something with `import Data.List hiding (sort)`
-- You can name imports with `import qualified Data.List as L`, and
-- then use them with `L.nub`.
import Data.List (nub)
-- Returns number of unique elements in a list.
uniqueCount :: (Eq a) => [a] -> Int
uniqueCount = length . nub
main = do
print (uniqueCount ([] :: [Int])) -- 0
print (uniqueCount [1]) -- 1
print (uniqueCount [1,2,1,2,4,3,3]) -- 4
You create own modules with the module
keyword.
-- You could import this module with `import Geometry` if
-- `Geometry.hs` is in the same folder.
module Geometry
( sphereVolume
, sphereArea
) where
sphereVolume :: Float -> Float
sphereVolume radius = (4.0 / 3.0) * pi * (radius ^ 3)
sphereArea :: Float -> Float
sphereArea radius = 4 * pi * (radius ^ 2)
You should use submodules to keep your main modules clean.
-- Directory `Geometry` with `Sphere.hs`.
module Geometry.Sphere
( volume
, area
) where
volume :: Float -> Float
volume radius = (4.0 / 3.0) * pi * (radius ^ 3)
area :: Float -> Float
area radius = 4 * pi * (radius ^ 2)
Monads
Monads are one of the fundamental concepts of Haskell. They should be thought as computation builders which chain operations together. Understanding monads is as important as understanding conditional statements in other programming languages.
-- Input/output actions work using the IO monad.
main = print "Hello world!" -- "Hello world!"
-- Computations that might return nothing work using the Maybe monad.
-- Value is Just x or Nothing.
main = print (Just "Hello!") -- Just "Hello!", a Maybe String
-- List comprehension works using the List monad.
main = print [x*2 | x <- [1..10], odd x] -- [2,6,10,14,18]
Monad is basically something that wraps a value or values (e.g. Int
, String
) while implementing instructions how to wrap a value, unwrap the value and apply the wrapped value to a function. return
, >>=
, >>
and fail
are the functions that must be implemented but most of the time the default implementations >>
and fail
work nicely. >>=
is called bind.
-- Example definition of the Maybe monad.
data Maybe a = Nothing | Just a
instance Monad Maybe where
Nothing >>= f = Nothing -- calling a function while wrapping Nothing
(Just x) >>= f = f x -- calling a function while wrapping Just value
return = Just -- minimal warpper for a value
Both list comprehension and do-notation are just syntactical sugar for monad operators.
-- Do-notation.
main = do
print (succ 8) -- 9
print (min 19 20) -- 19
print (max 19 20) -- 20
-- Do-notation behind the scenes.
main =
print (succ 8) >> -- 9
print (min 19 20) >> -- 19
print (max 19 20) -- 20
-- List comprehension.
main = print [x*2 | x <- [1..10], odd x] -- [2,6,10,14,18]
-- List comprehension behind the scenes.
main = print ([1..10] >>= (\x -> if odd x then [x*2] else []))
-- [2,6,10,14,18]
Types
Every expression has a type. You can get the type of an expression with :t
in GHCi.
> :t True
True :: Bool
> :t "Hello"
"Hello" :: [Char]
> :t not
not :: Bool -> Bool
-- basic types
Int -- Bound so has max and min. Range depends on the computer word size.
-- You can get the range with `minBound :: Int` and `maxBound :: Int`.
Integer -- Not bound so can store big numbers.
Float -- Real floating-point number with single precision.
Double -- Real floating-point number with double precision.
Bool -- Boolean, True or False.
Char -- An unicode character, denoted by single quotes ''.
String -- A list of chars so the same as [Char], denoted by double quotes "".
-- a little bit more complex types
Num -- Int, Integer, Float and Double are all Num (numbers).
Floating -- Float and Double are floating-point numbers.
Integral -- Int and Integer are integral numbers.
Ord -- Type has a comparable order.
::
operator makes an explicit type declaration. ::
is read "has type of".
-- removeNonUppercase "has type of" function that takes
-- a string and returns a string.
removeNonUppercase :: [Char] -> [Char]
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]
main = do
print (removeNonUppercase "Hello There!") -- "HT"
print (10 :: Double) -- 10.0 "has type of" Double
show
returns the string representation. The counter operation read
turns a string into another type, but sometimes you must define the type if compiler cannot figure it out from the context.
main = do
print (1) -- 1
print (show 1) -- "1"
print (read "1" + 10) -- 11
print ((read "1") :: Int) -- 1
data
defines a new data type.
-- Noobean can be `No` or `Yes`.
data Noobean = No | Yes
data Point = Point Float Float
-- We have two kinds of shapes, circles and rectangles.
-- Circle and Rectangle are value constructors => they are functions, not types.
data Shape = Circle Point Float | Rectangle Point Point
area :: Shape -> Float
area (Cirle _ r) = pi * r ^ 2
area (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
deriving
allows specifying the other types that the data type implements.
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Eq, Ord, Show, Read, Bound, Enum)
-- Eq = values have a concept of being equal `==`
-- Ord = values has order, Monday is the lowest, Sunday is the highest
-- Show = can be turned to string
-- Read = can be turned from to string to this type
-- Bound = has maximum and minimum value
-- Enum = values have predecessor and/or successor.
Data types also have an alternative record syntax.
data Person = Person
{ firstName :: String
, lastName :: String
, age :: Int
} deriving (Show)
tommi = Person {firstName = "Tommi", lastName = "Laine", age = 26}
main = do
print tommi -- Person {firstName = "Tommi", lastName = "Laine", age = 26}
print (lastName tommi) -- "Laine"
Type constructors are predefined templates to create new data types.
-- `Maybe` is a type constructor that creates a new type when used.
-- data Maybe a = Nothing | Just a
-- E.g. Maybe String creates a new type which may be a string or nothing.
main = do
print (Just "Hello!" :: Maybe String) -- Just "Hello!", explicit
print (Just "Hello!") -- Just "Hello!", implicit
print (Nothing :: Maybe String) -- Nothing
type
creates a type alias. It doesn't create a new type, it only creates a reference to an existing type.
type PenguinCount = Int
main = print (10 :: PenguinCount) -- 10
class
allows defining data type behaviour; a type class.
-- Here is how `Eq` is implemented in the standard library.
-- class Eq a where
-- (==) :: a -> a -> Bool
-- (/=) :: a -> a -> Bool
-- x == y = not (x /= y)
-- x /= y = not (x == y)
instance
allows implementing shared data type behaviour.
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"
main = print (Yellow :: TrafficLight) -- Yellow light
Lists
Lists can only have elements of one type.
myNumbers = [4,8,15,16,32,42]
main = print myNumbers -- [4,8,15,16,32,42]
!!
is list access. This should rarely be needed though.
myNumbers = [4,8,15,16,32,42]
main = print (myNumbers !! 4 ) -- 32
++
is concatenation. Notice that ++
must go through the first elemnt fully before concetenating.
smallNumbers = [2,4,7]
bigNumbers = [999,1234,7890]
firstName = "Tommi"
lastName = "Laine"
main = do
print (smallNumbers ++ bigNumbers) -- [2,4,7,999,1234,7890]
print (firstName ++ " " ++ lastName) -- "Tommi Laine"
:
is prepend. Much more performant than concatenation, but requires a single list element that is added to the list.
lastName = "Laine"
main = print ('T':'o':'m':'m':'i':' ':lastName) -- "Tommi Laine"
Strings are only lists of characters. You can use list functions on strings.
main = print ("Ruksi" == ['R','u','k','s','i']) -- True
Comparison of lists is done by individual elements. It first takes the first elements of both lists and compares them until the lower or greater is found.
main = do
print ([3,1,1] > [2,4,0]) -- True, because 3 > 2
print ([3,2,1] > [2,3,0,0]) -- True, because 3 > 2
print ([3] > [2,1,0,0]) -- True, because 3 > 2
print ([3,3,0] > [3,2,1]) -- True, because 3 == 3, but 3 > 2
Basic list functions.
main = do
print (head [3,2,1]) -- 3
print (tail [3,2,1]) -- [2,1]
print (last [3,2,1]) -- 1
print (init [3,2,1]) -- [3,2]
print (reverse [3,2,1]) -- [1,2,3]
print (take 2 [3,2,1]) -- [3,2]
print (take 4 [3,2,1]) -- [3,2,1]
print (drop 2 [3,2,1]) -- [1]
print (drop 4 [3,2,1]) -- []
print (maximum [3,2,1]) -- 3
print (minimum [3,2,1]) -- 1
print (sum [3,2,2]) -- 7
print (product [3,2,2]) -- 12
-- NOTE: Doing any of the previous on an empty lists will crash.
-- Checking if list contains values.
print (length [3,2,1]) -- 3
print (null [3,2,1]) -- False
print (null []) -- True
-- Checking if list contains a specific element.
print (8 `elem` [3,2,1]) -- False
print (8 `elem` [3,8,1]) -- True
Ranges
You can generate lists using ranges.
main = do
print [1..10] -- [1,2,3,4,5,6,7,8,9,10]
-- You can specify first two elements and Haskell figures out the step.
print [1,2..10] -- [1,2,3,4,5,6,7,8,9,10]
print [10,9..1] -- [10,9,8,7,6,5,4,3,2,1]
print [2,4..10] -- [2,4,6,8,10]
-- Ranges work with characters.
print ['a'..'z'] -- "abcdefghijklmnopqrstuvwxyz"
print ['K'..'O'] -- "KLMNO"
Ranges can be infinite but then you can utilize only part of it.
main = print (take 10 [3,5..]) -- [3,5,7,9,11,13,15,17,19,21]
Avoid using floats with ranges. Or prepare to fix a lot of bugs.
main = print [0.3,0.6..0.9] -- [0.3,0.6,0.8999999999999999]
Additional functions to generate lists.
main = do
print (take 10 (cycle [1,2,3])) -- [1,2,3,1,2,3,1,2,3,1]
print (take 11 (cycle "WAT ")) -- "WAT WAT WAT"
print (take 3 (repeat 10)) -- [10,10,10]
print (replicate 3 10) -- [10,10,10]
List Comprehension
List comprehension allows reading and manipulating a list using a function.
main = do
-- We draw parameter from the source by binding them to the x.
-- [output_element | input_element <- source, optional filters]
print [x * 2 | x <- [1..10]] -- [2,4,6,8,10,12,14,16,18,20]
You can add predicates in list comprehension to filter lists.
main = do
-- All numbers between 1 and 10 that leave 2 behind after dividing with 3.
print [x | x <- [1..10], x `mod` 3 == 2] -- [2,5,8]
tickTock xs = [if x < 5 then "tick" else "tock" | x <- xs, odd x]
main = do
print [1..10] -- [1,2,3,4,5,6,7,8,9,10]
print [x | x <- [1..10], odd x] -- [1,3,5,7,9]
print (tickTock [1..10]) -- ["tick","tick","tock","tock","tock"]
main = do
-- You can specify multiple predicates.
print [x | x <- [1..10], x /= 3, x /= 5, x /= 9] -- [1,2,4,6,7,8,10]
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]
main = do
-- Strings are lists too.
print (removeNonUppercase "What is this Madness.") -- "WM"
xss = [[1..4],[1..6],[1..10]]
main = do
-- Nested lists.
print [[x | x <- xs, even x] | xs <- xss] -- [[2,4],[2,4,6],[2,4,6,8,10]]
Single list comprehension may draw parameters from multiple lists. Note that elements are combined in all the possible ways.
listA = [1..3]
listB = [40,50,60]
main = do
print listA -- [1,2,3]
print listB -- [40,50,60]
print [x + y | x <- listA, y <- listB ] -- [41,51,61,42,52,62,43,53,63]
print [x + y | x <- listA, y <- listB, x + y > 60] -- [61,62,63]
myLength xs = sum [1 | _ <- xs]
main = do
print (myLength [1..3]) -- 3
print (myLength [10..100]) -- 91
Tuples
Tuples are used to store several values as a single value.
main = do
print (1,2,3) -- (1,2,3)
print (6,'a',"Cain") -- (6,'a',"Cain")
Tuple's size and contained types define the tuple's type.
main = do
print [(1,2), (3,4)] -- [(1,2),(3,4)]
print [(1,2), (3,4)] -- [(1,2),(3,4)]
print [('a','b'), ('c','d')] -- [('a','b'),('c','d')]
-- print [(1,2), (3,4,5)] -- This would crash, size mismatch.
-- print [(1,2), (3,'b')] -- This would crash, element type mismatch.
print ((1,2) == (1,2)) -- True
print ((1,2) == (2,1)) -- False
-- print ((1,2) == (1,2,4)) -- This would crash, size mismatch.
Pair is the most common tuple type. fst
(first) and snd
(second) functions only work on pairs.
main = do
print (fst ('a',9)) -- 'a'
print (snd ('a',8)) -- 8
zip
creates pairs.
main = do
print (zip [1,2] [5,5]) -- [(1,5),(2,5)]
print (zip [1,2,3,4] [5,5,5,5]) -- [(1,5),(2,5),(3,5),(4,5)]
print (zip [1,2,3,4] (repeat 5)) -- [(1,5),(2,5),(3,5),(4,5)]
print (zip [1..] ["orange", "apple"]) -- [(1,"orange"),(2,"apple")]
-- Notice that extra elements are ignored on both sides.
You can generate all kinds of tuples with list comprehension.
-- All possible combinations of (a,b,c), where a, b and c are between 1 - 5.
triples = [(a,b,c) | a <- [1..5], b <- [1..5], c <- [1..5]]
main = print triples
Pattern Matching
Use pattern matching. Pattern matching allows conditional execution depending on the parameters.
sayMe :: Int -> String
sayMe 1 = "One!"
sayMe 2 = "Two!"
sayMe 3 = "Three!"
sayMe _ = "Not between 1 and 5!"
main = do
print (sayMe 1) -- "One!"
print (sayMe 2) -- "Two!"
print (sayMe 3) -- "Three!"
print (sayMe 4) -- "Not between 1 and 5!"
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
main = print (factorial 20) -- 2432902008176640000
--- Pattern matching works with tuples too.
addVectors :: (Int, Int) -> (Int, Int) -> (Int, Int)
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
second :: (Int, Int, Int) -> Int
second (_, y, _) = y
main = do
print (addVectors (1,1) (1,2)) -- (2,3)
print (addVectors (1,5) (-1,2)) -- (0,7)
print (second (9,0,7)) -- 0
Include a "catch all" pattern as the final pattern. It might be raising an error but define it so next reader can understand that the error is intentional.
-- This is usually bad.
getCode :: Char -> String
getCode 'a' = "Alpha"
getCode 'b' = "Beta"
getCode 'c' = "Charlie"
-- should have getCode x = error "not defined for x" or something
main = do
print (getCode 'a') -- "Alpha"
print (getCode 'd') -- Non-exhaustive patterns in function getCode...
print (getCode 'b') -- Never gets called as crashes on the previous line.
Handle empty values in patterns. Operating on an emtpy lists causes frequent problems. At least use Error
monad to indicate that what you are doing is intentional.
firstLetter :: String -> String
firstLetter "" = error "firstLetter: empty string"
firstLetter all@(x:xs) = "The first letter of "++ all ++ " is " ++ [x]
main = do
print (firstLetter "Ruksi") -- "The first letter of Ruksi is R"
print (firstLetter "") -- Crashes here.
print (firstLetter "Batman") -- Not called as crashed.
tell :: (Show a) => [a] -> String
tell [] = "An empty list."
tell (x:[]) = "List with one element: " ++ show x
tell (x:y:[]) = "List with two elements: " ++ show x ++ " and " ++ show y
tell (x:y:_) = "List has more than two elements."
main = do
print (tell ([] :: [Int])) -- We must define the array type in this example.
print (tell [1])
print (tell [1,2])
print (tell [1,2,3])
Guards |
are used to extend pattern matching allowing conditional checks.
bmiTell :: Double -> String
bmiTell bmi
| bmi <= 18.5 = "You're underweight."
| bmi <= 25.5 = "You're normal."
| bmi <= 30.5 = "You're fat."
| otherwise = "You're lying."
max' :: (Ord a) => a -> a -> a
max' a b
| a <= b = b
| otherwise = a
main = do
print (bmiTell 10.3) -- "You're underweight."
print (bmiTell 24.9) -- "You're fat."
print (bmiTell 42.42) -- "You're lying."
print (max' 10 1) -- 10
print (max' 9 100) -- 100
Prefer pattern matching over case
or guards.
-- Case variant.
tellInt :: Int -> String
tellInt i = case i of
0 -> "Zero!"
1 -> "One!"
2 -> "Two!"
_ -> "Other!"
-- Pattern matching variant.
tellInt' :: Int -> String
tellInt' 0 = "Zero!"
tellInt' 1 = "One!"
tellInt' 2 = "Two!"
tellInt' _ = "Other!"
-- Guard variant.
tellInt'' :: Int -> String
tellInt'' num
| num == 0 = "Zero!"
| num == 1 = "One!"
| num == 2 = "Two!"
| otherwise = "Other!"
main = do
print (tellInt 0) -- "Zero!"
print (tellInt 3) -- "Other!"
print (tellInt' 0) -- "Zero!"
print (tellInt' 3) -- "Other!"
print (tellInt'' 0) -- "Zero!"
print (tellInt'' 3) -- "Other!"
where
where
defines temporary variables in guards and functions. Variables assinged in where
are only visible to the previous function. For example, variables defined in a where
are only visible to the pattern the where
follows.
myFunc :: Float -> Float -> Float -> String
myFunc x y z
| half > reallyBigHalf = "Total is really big!"
| total > 10 = "Total is more than 10."
| total < 10 = "Total is less than 10."
where total = x + y + z
half = total / 2
reallyBigHalf = 100
main = do
print (myFunc 1.0 2.5 3.6) -- "Total is less than 10."
print (myFunc 4.1 5.2 3.3) -- "Total is more than 10."
print (myFunc 300.0 3.0 1.0) -- "Total is really big!"
You can use pattern matching in where
.
initials :: String -> String -> String
initials firstName lastName = [f] ++ ". " ++ [l] ++ "."
where (f:_) = firstName
(l:_) = lastName
main = print (initials "Tommi" "Laine") -- "T. L."
let - in
let - in
defines temporary variables in a designated scope.
cylinderSurfaceArea :: Double -> Double -> Double
cylinderSurfaceArea radius height =
let sideArea = 2 * pi * radius * height
topArea = pi * radius ^ 2
in sideArea + 2 * topArea
main = print (cylinderSurfaceArea 10.0 10.0) -- 1256.6370614359173
Use let
to dismantle tuples.
myTuple = (1,2,3)
main = print ((let (x,y,z) = myTuple in x + y + z) * 100) -- 600
Use let
in list comprehensions.
calcBigBmis :: [(Double, Double)] -> [Double]
calcBigBmis xs = [bmi | (w,h) <- xs, let bmi = w / h ^ 2, bmi > 25.0]
Use let
to create a function in a closed scope.
main = print (let square x = x * x in (square 5, square 3)) -- (25,9)
Folds
Folds apply a function to each list element, passing the result to the next element. foldl
starts from the start of the list, foldr
starts from the end of the list.
-- Initial value of 0 is given to the fold.
sum' :: (Num a) => [a] -> a
sum' xs = foldl (\acc x -> acc + x) 0 xs
-- `foldl` returns a function that takes a list, so we can use currying to
-- make the function shorter.
sum'' :: (Num a) => [a] -> a
sum'' = foldl (+) 0
-- Take the last element, use function on it and push it as the
-- first element of the accumalator.
map' :: (a -> b) -> [a] -> [b]
map' f xs = foldr (\x acc -> f x : acc) [] xs
main = do
print (sum' [1,2,3]) -- 6
print (sum'' [1,2,3]) -- 6
print (map' (+ 1) [1,2,3]) -- [2,3,4]
fold1l
and fold1r
work as previous folds, but take the first or last element as the staring accumalator. Thus, they will cause a runtime error when used with an empty list.
scanl
and scanr
work like left fold and right fold, but return the accumalator states in a form of a list.
main = do
print (scanl (+) 0 [1,2,3]) -- [0,1,3,6]
print (scanr (+) 0 [1,2,3]) -- [6,5,3,0]
Normal folds may cause stack overflow if used on very long lists because the whole list is saved in memory before they are used. Module Data.List
provides strict fold versions that calculate accumalator value as they go e.g. foldl'
.
import Data.List (foldl')
myFoldl = foldl (+) 0 (replicate 100000000 1) -- bad
myFoldl' = foldl' (+) 0 (replicate 100000000 1)
main = do
-- print myFoldl -- may stack overflow
print myFoldl' -- 100000000
Maps
Association lists can be used to create mapping from keys to values.
phoneBook =
[("betty", "555-2938")
,("bonnie", "452-6972")
,("joe", "413-5498")
]
Haskell also has a type dedicated to this task.
import qualified Data.Map as Map
intToGreek = Map.fromList [(1, "alpha"), (2, "beta"), (3, "gamma")]
main = do
print (Map.lookup 1 intToGreek) -- Just "alpha"
print (Map.lookup 4 intToGreek) -- Nothing
print (Map.lookup 4 intToGreek') -- Just "delta"
where intToGreek' = Map.insert 4 "delta" intToGreek
Functors
Functor
class type tells how a function should be applied to a given type. It's usually defined that a type should be an instance of Functor
if it can be mapped over.
-- Functor type class.
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- For `a` that can be turned into `b`, when a function is applied to `a`,
-- how do we apply it to `b`.
-- For example, `a` can be a list, `b` can be one or multiple integers.
-- Functor implementation for Maybe.
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
-- Functor implementation for Tree.
instance Functor Tree where
fmap f EmptyTree = EmptyTree
fmap f (Node x left right) = Node (f x) (fmap f left) (fmap f right)
Applicatives
Applicative
is a more advanced version of Functor
. They are functors that can be applied to each other. It's in the Control.Applicative
module.
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
-- Applicative implementation for Maybe.
instance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
(Just f) <*> something = fmap f something
pure
is used to embed pure expressions. pure
takes a value and puts it in a minimal context that still yields that value.
import Control.Applicative
main = do
print (pure "Hey" :: [String] ) -- ["Hey"]
print (pure "Hey" :: Maybe String) -- Just "Hey"
Sequential application <*>
. <*>
inflix function takes a Functor
that has a function in it and another Functor
. Then it extracts the first function and maps it over the second Functor
. Note that Applicative
s are also Functor
s.
import Control.Applicative
main = do
print (Just (+3) <*> Just 9) -- Just 12
print (pure (+3) <*> Just 10) -- Just 13
print (Just (++"hahah") <*> Nothing) -- Nothing
print (pure (+) <*> Just 3 <*> Just 5) -- Just 8
print (pure (+) <*> Nothing <*> Just 5) -- Nothing
<$>
function is basically a fmap
as an infix operator. It helps wrapping a normal function as an applicative.
import Control.Applicative
main = do
print ((++) "johntra" "volta") -- "johntravolta"
print ((++) <$> Just "johntra" <*> Just "volta") -- Just "johntravolta"
LiftA
functions can be used to lift a function to actions. It's used to hide <$>
and <*>
syntax.
import Control.Applicative
main = do
print ((:) <$> Just 3 <*> Just [4]) -- Just [3,4]
print (liftA2 (:) (Just 3) (Just [4])) -- Just [3,4]
Error Handling
myFunction = error "oh noes."
main = do
myFunction -- "oh noes".
myFunction -- not called because of the exception.
Sources
- Learn You a Haskell for Great Good, Miran Lipovača
- All About Monad