Joel E. Kaasinen
Spring 2018
opqdonut
on IRChumppa
on IRC#ifp2018
on IRCNet#haskell
on freenodestack
tool, see http://haskellstack.orgstack ghci
to get an interactive haskell shellghci
is the interactive Haskell interpreter$ stack ghci
GHCi, version 8.0.1: http://www.haskell.org/ghc/ :? for help
Prelude> 1+1
2
Prelude> "asdf"
"asdf"
Prelude> reverse "asdf"
"fdsa"
Prelude> :t "asdf"
"asdf" :: [Char]
Prelude> tail "asdf"
"sdf"
Prelude> :t tail "asdf"
tail "asdf" :: [Char]
Prelude> :t tail
tail :: [a] -> [a]
expression :: type
Expression | Type | Value |
---|---|---|
1+1 |
Int |
2 |
not True |
Bool |
False |
f 1
is the same as f(1)
in other languagesHaskell | C |
---|---|
f 1 |
f(1) |
f 1 2 |
f(1,2) |
g f 1 |
g(f,1) |
g (f 1) |
g(f(1)) |
a + b |
a + b |
f a + g b |
f(a) + g(b) |
f (a + g b) |
f(a+g(b)) |
f a (g b) |
f(a,g(b)) |
Type | Literals | Use | Operations |
---|---|---|---|
Int |
1 , 2 , -3 |
The usual number type | + , - , * , div , mod |
Integer |
1 , 2 , 900000000000000000 |
Unbounded number type | + , - , * , div , mod |
Double |
0.1 , 1.2e5 |
Floating point numbers | + , - , * , / , sqrt |
Bool |
True , False |
Truth values | && , || , not |
String |
"abcd" , "" |
Strings of characters | reverse , ++ |
argumentType -> returnType
arg1Type -> arg2Type -> returnType
a1 -> a2 -> a3 -> p
if
is a statementif
is an expression?:
operator in C or JavaJava:
Haskell:
if
returns a value. That’s why you always need an else
!let...in
and where
where
adds local definitions to a definition:let...in
makes an expression:let
and where
, but mostly you can use which ever you likegreet :: String -> String -> String
greet "Suomi" name = "Hei. " ++ name
greet "Italia" name = "Ciao bella! " ++ name
greet "Englanti" name = "How do you do? " ++ name
greet _ name = "Hello. " ++ name
Java:
public int fibonacci(int n) {
int a = 0;
int b = 1;
while (n>1) {
int c = a+b;
a=b;
b=c;
n--;
}
return b;
}
Haskell:
module Collatz where
step :: Integer -> Integer
step x = if even x then down else up
where down = div x 2
up = 3*x+1
collatz :: Integer -> Integer
collatz 1 = 0
collatz x = 1 + collatz (step x)
longest :: Integer -> Integer
longest upperBound = longest' 0 upperBound
longest' :: Integer -> Integer -> Integer
longest' l 0 = l
longest' l n = if l' > l
then longest' l' (n-1)
else longest' l (n-1)
where l' = collatz n
i x = let y = x+x+x+x+x+x in div y 5
j x = let y = x+x+x
+x+x+x
in div y 5
k = a + b
where a = 1
b = 1
l = a + b
where
a = 1
b = 1
f x y
is always the same if the values x
and y
are the same
f x y
print somethingifp2018-exercises
github repository
README.md
file in the repository for instructionsW1.hs
edit this according to the instructions to complete the exercisesW1Test.hs
run this to check your workjoel.kaasinen@gmail.com
W1.Surname.hs
!if then else
is often a bit cumbersomeBool
True
is chosenotherwise
is just an alias for True
[True,True,False] :: [Bool]
["Moi","Hei"] :: [String]
[] :: [a] -- more about this later
[[1,2],[3,4]] :: [[Int]]
-- returns the first element
head :: [a] -> a
-- returns everything except the first element
tail :: [a] -> [a]
-- returns the n first elements
take :: Int -> [a] -> [a]
-- returns everything except the n first elements
drop :: Int -> [a] -> [a]
-- lists are catenated with the ++ operator
(++) :: [a] -> [a] -> [a]
-- lists are indexed with the !! operator
(!!) :: [a] -> Int -> a
==
operatorString
is just an alias for [Char]
, which means a list of characters
a
, b
, thisIsATypeVariable
Bool
) by the process of type inference (also called unification)Prelude> [True,False] ++ "Moi"
<interactive>:1:16:
Couldn't match expected type `Bool' against inferred type `Char'
Expected type: [Bool]
Inferred type: [Char]
In the second argument of `(++)', namely `"Moi"'
In the expression: [True, False] ++ "Moi"
1
and n
k
follow the character c
in the string s
?-- These are from the Data.List module in the standard library
tails :: [a] -> [[a]] -- return a list of suffixes of a given list
sort :: Ord a => [a] -> [a] -- sorts a list
Prelude> (f True) 1 2 3
4
Prelude> let g = (f True) in g 1 2 3
4
Prelude> let g = (f True 1) in g 2 3
4
Prelude> map (f True 1 2) [1,2,3]
[2,3,4]
.
operator.
(f.g) x ==> f (g x)
$
operator$
operator is more subtlef x y z $ g z w
is the same as (f x y z) (g z w)
as
Prelude> (\x -> x*x) 3
9
Prelude> (\x -> reverse x == x) "ABBA"
True
Prelude> filter (\x -> reverse x == x) ["ABBA","ACDC","otto","lothar","anna"]
["ABBA","otto","anna"]
Prelude> (\x y -> x^2+y^2) 2 3
13
is the same as
contexts
function using .
, $
and lambdascontexts :: Int -> Char -> String -> [String]
contexts k c s = sort (map tail (filter match (map process (tails s))))
where match [] = False
match (c':_) = c==c'
process x = take (k+1) x
process
:contexts k c s = sort (map tail (filter match (map (take (k+1)) (tails s))))
where match [] = False
match (c':_) = c==c'
.
and $
to eliminate parentheses:contexts k c s = sort . map tail . filter match . map (take $ k+1) $ tails s
where match [] = False
match (c':_) = c==c'
match
:-- find the first substring that consists only of the given characters
findSubString :: [String] -> [String] -> [String]
findSubString chars = takeWhile (\x -> elem x chars)
. dropWhile (\x -> not $ elem x chars)
-- from the module Data.Ord
-- compares two values "through" the function f
comparing :: (Ord a) => (b -> a) -> b -> b -> Ordering
comparing f x y = compare (f x) (f y)
-- from the module Data.List
-- sorts a list using the given comparison function
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
-- sorts lists by their length
sortByLength :: [[a]] -> [[a]]
sortByLength = sortBy (comparing length)
:
Prelude> 1:[]
[1]
Prelude> 1:[2,3]
[1,2,3]
Prelude> tail (1:[2,3])
[2,3]
Prelude> head (1:[2,3])
1
Prelude> :t (:)
(:) :: a -> [a] -> [a]
:
builds a list, out of a head and a tail:
is actually the constructor for lists: it returns a new linked list node[1,2,3]
is structured: (:)
/ \
1 (:)
/ \
2 (:)
/ \
3 []
:
is a constructor, we can pattern match it
[]
, the empty listmyhead :: [Int] -> Int
myhead [] = -1
myhead (first:rest) = first
mytail :: [Int] -> [Int]
mytail [] = []
mytail (first:rest) = rest
(a:b:_)
is the same as (a:(b:_))
:map
:filter
:Prelude> repeat 1
[1,1,1,1,
^C
Prelude> take 10 $ repeat 1
[1,1,1,1,1,1,1,1,1,1]
Prelude> take 20 $ repeat 1
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
Prelude> repeat 1 !! 13337
1
Prelude> take 21 $ cycle "asdf"
"asdfasdfasdfasdfasdfa"
Prelude> take 4 . map (take 4) . tails $ cycle "asdf"
["asdf","sdfa","dfas","fasd"]
head (filter (>100) (map (3^) [0..]))
==> head (filter (>100) (map (3^) (0:[1..])))
==> head (filter (>100) (1 : map (3^) [1..]))
==> head (filter (>100) (map (3^) [1..]))
==> head (filter (>100) (map (3^) (1:[2..])))
==> head (filter (>100) (3 : map (3^) [2..]))
==> head (filter (>100) (map (3^) [2..]))
-- let's take bigger steps now
==> head (filter (>100) (9 : map (3^) [3..]))
==> head (filter (>100) (27 : map (3^) [4..]))
==> head (filter (>100) (81 : map (3^) [5..]))
==> head (filter (>100) (243 : map (3^) [6..]))
==> head (243 : filter (>100) (map (3^) [6..]))
==> 243
Bool
and Ordering
:data
syntax can be used for other kinds of datatypes as well. Here we define a report type that contains an id number, a title, and a body:data
declaration are called constructors
True
, False
, LT
, MkReport
, …|
syntaxderiving Show
Prelude> EQ
EQ
Prelude> True
True
Prelude> Joker
<interactive>:1:0:
No instance for (Show Card)
arising from a use of `print' at <interactive>:1:0-4
Possible fix: add an instance declaration for (Show Card)
In a stmt of a 'do' expression: print it
deriving Show
:deriving
and what Show
is later-1
– ugly, not always possibleMaybe
type – pure and safeMaybe a
is the type with the values Nothing
and Just x
, where x :: a
Type | Values |
---|---|
Maybe Bool |
Nothing , Just False , Just True |
Maybe Int |
Nothing , Just 0 , Just 1 , … |
Maybe [Int] |
Nothing , Just [] , Just [1,1337] , … |
Maybe a
as being a bit like [a]
except there can only be 0 or 1 elements, not more.Maybe
is easy. Just pattern match:intOrZero :: Maybe Int -> Int
intOrZero Nothing = 0
intOrZero (Just i) = i
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x
headOrZero :: [Int] -> Int
headOrZero xs = case safeHead xs of Nothing -> 0
Just x -> x
Maybe
is:a
? We’ll find out next.Maybe Integer
, Optional<Integer>
Prelude> :t MkWrap
MkWrap :: a -> Wrap a
Prelude> :t unwrap
unwrap :: Wrap a -> a
Prelude> :t MkWrap True
MkWrap True :: Wrap Bool
Prelude> :t MkWrap []
MkWrap [] :: Wrap [a]
a
, map
, xs
, …Maybe
, Just
, Card
, Heart
, …Nothing
Either
:Maybe a
let us return a value of type a
or nothingEither a b
lets us return a value of type a
or a value of type b
readInt :: String -> Either String Int
readInt "0" = Right 0
readInt "1" = Right 1
readInt s = Left ("Unsupported string "++show s)
Either
to signal when to stop iterating[a]
Leaf
a
and two child treestreeHeight :: Tree a -> Int
treeHeight Leaf = 0
treeHeight (Node _ l r) = 1 + max (treeHeight l) (treeHeight r)
treeHeight Leaf ==> 0
treeHeight (Node 2 Leaf Leaf)
==> 1 + max (treeHeight Leaf) (treeHeight leaf)
==> 1 + max 0 0
==> 1
treeHeight (Node 1 Leaf (Node 2 Leaf Leaf))
==> 1 + max (treeHeight Leaf) (treeHeight (Node 2 Leaf Leaf))
==> 1 + max 0 1
==> 2
treeHeight (Node 0 (Node 1 Leaf (Node 2 Leaf Leaf)) Leaf)
==> 1 + max (treeHeight (Node 1 Leaf (Node 2 Leaf Leaf))) (treeHeight Leaf)
==> 1 + max 2 0
==> 3
insert :: Int -> Tree Int -> Tree Int
insert x Leaf = Node x Leaf Leaf
insert x (Node y l r)
| x < y = Node y (insert x l) r
| x > y = Node y l (insert x r)
| otherwise = Node y l r
lookup :: Int -> Tree Int -> Bool
lookup x Leaf = False
lookup x (Node y l r)
| x < y = lookup x l
| x > y = lookup x r
| otherwise = True
++
:map
and filter
:[(x,y) | x <- [1..7], even x, y <- [True,False]]
==> [(2,True),(2,False),(4,True),(4,False),(6,True),(6,False)]
!#$%&*+./<=>?@\^|-~
`foo`
-syntax:RealWorld -> (a,RealWorld)
case of
case of
expression that’s been mentioned a couple of times works:IO a
is an operation that produces a value of type a
()
is the so called unit type, its only value is ()
Haskell type | Java-type |
---|---|
foo :: IO () |
void foo() |
bar :: IO a |
a bar() |
f :: a -> b -> IO () |
void f(a arg0, b arg1) |
g :: c -> IO d |
d g(c arg) |
do
as thou wiltdo
syntax:do operation
operation arg
variable <- operationThatReturnsStuff
let var2 = expression
operationThatProducesTheResult var2
return
return
takes a value and turns it into an operation, that returns that valueproduceThree :: IO Int
produceThree = return 3
printThree :: IO ()
printThree = do
three <- produceThree
putStrLn $ show three
do
it is:return
pitfallsreturn
is a function that takes a value and returns an operation. Nothing else.do
-notation, the last line decides which value to produce2
:return
is a function, you should remember to parenteshize any complex expressionsdo
and typesdo
builds a value of type IO <something>
lastOp
must be of type IO X
foo
will also be IO X
lastOp
must be of type Y -> IO X
foo
will be A -> B -> IO X
(for x :: A
and y :: B
)foo
will have type A -> IO B
, where x :: A
and arg :: B
<-
op :: IO X
and you have var <- op
, var
will have type X
<-
“opens up the IO
box”do
cannot be foo <- bar
return something
)IO
<-
gives you X
from IO X
<-
inside do
do
always means a value of type IO Y
IO
wrapper, but you must return to itIO
worldprintDescription :: Int -> IO ()
printDescription n
| even n = putStrLn "even"
| n==3 = putStrLn "three"
| otherwise = print n
IO
-specific control structures (in the module Control.Monad
)-- conditional operation
when :: Bool -> IO () -> IO ()
-- conditonal operation, vice versa
unless :: Bool -> IO () -> IO ()
-- do something many times
replicateM :: Int -> IO a -> IO [a]
-- do something many times, throw away the results
replicateM_ :: Int -> IO a -> IO ()
-- do something for every list element
mapM :: (a -> IO b) -> [a] -> IO [b]
-- do something for every list element, throw away the results
mapM_ :: (a -> IO b) -> [a] -> IO ()
-- the same, but arguments flipped
forM :: [a] -> (a -> IO b) -> IO [b]
forM_ :: [a] -> (a -> IO b) -> IO ()
-- printing
putStr :: String -> IO ()
putStrLn :: String -> IO ()
print :: Show a => a -> IO ()
print = putStr . show
-- reading
getLine :: IO String
readLn :: Read a => IO a
System.IO
):-- simple operations:
-- FilePath is just String
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
-- with file handles:
openFile :: FilePath -> IOMode -> IO Handle -- IOMode is either ReadMode, WriteMode, or ReadWriteMode
hPutStr :: Handle -> String -> IO ()
hPutStrLn :: Handle -> String -> IO ()
hPrint :: (Show a) => Handle -> a -> IO ()
hGetLine :: Handle -> IO String
-- etc
-- split a string into lines
lines :: String -> [String]
-- build a string out of lines
unlines :: [String] -> String
-- split a string into words
words :: String -> [String]
-- build a string out of words
unwords :: [String] -> String
-- render a value into a string
show :: Show a => a -> String
-- read a string into a value (opposite of show!)
read :: Read a => String -> a
.hs
files under this directory:-- a line is a type signature if it contains :: but does not contain =
isTypeSignature :: String -> Bool
isTypeSignature s = not (isInfixOf "=" s) && isInfixOf "::" s
-- return list of types for a .hs file
readTypesFile :: FilePath -> IO [String]
readTypesFile file
| isSuffixOf ".hs" file = do content <- readFile file
let ls = lines content
return $ filter isTypeSignature ls
| otherwise = return []
-- list children of directory, prepend directory name
qualifiedChildren path = do childs <- listDirectory path
return $ map (\name -> path++"/"++name) childs
readTypesDir :: FilePath -> IO [String]
readTypesDir path = do childs <- qualifiedChildren path
typess <- forM childs readTypes
return $ concat typess
-- read types contained in a file or directory
readTypes :: FilePath -> IO [String]
readTypes path = do isDir <- doesDirectoryExist path
if isDir then readTypesDir path else readTypesFile path
main = do ts <- readTypes "."
mapM_ putStrLn ts
putStrLn :: String -> IO ()
is a pure function that returns an operationputStrLn x
is the same when x
is the samemain :: IO ()
main
choice :: IO a -> IO a -> IO a
choice a b =
do putStr "a or b? "
x <- getLine
case x of "a" -> a
"b" -> b
_ -> do putStrLn "Wrong!"
choice a b
&&
operator, which is useful in the presence of side effectsb
happen only if a
returns True
opc :: IO a -> (a -> IO b) -> (a -> IO ()) -> IO b
opc open process close = do
resource <- open
result <- process resource
close resource
return result
firstLineOfFile path = opc (openFile path ReadMode) hGetFile hClose
withFile path op = opc (openFile path ReadMode) op hClose
IORef a
is a mutable reference to a value of type a
newIORef :: a -> IO (IORef a)
readIORef :: IORef a -> IO a
writeIORef :: IORef a -> a -> IO ()
modifyIORef :: IORef a -> (a -> a) -> IO ()
IORef
:sumList :: [Int] -> IO Int
sumList xs = do r <- newIORef 0
mapM_ (\x -> modifyIORef r (x+)) xs
readIORef r
IORef
isn’t necessary most of the time. Prefer recursion, arguments and return valuesIO X
is an IO operation that produces a value of type Xdo
-notation:op :: X -> IO Y
op arg = do operation -- run operation
operation2 arg -- run operation with argument
result <- operation3 arg -- run operation with argument, store result
let something = f result -- run a pure function f, store result
finalOperation -- last operation produces the the return value
return x
is the operation that always produces value x
(a,b)
and (a,b,c)
and so on(1+1, True)
fst
and snd
on pairs:findWithIndex :: (a -> Bool) -> [a] -> (a,Int)
findWithIndex p xs = go 0 xs
where go i (x:xs)
| p x = (x,i)
| otherwise = go (i+1) xs
+
work on both Int
s and Double
s?==
?(Eq a) => a -> a -> Bool
means: for all types a
that belong to the class Eq
, this is a function of type a -> a -> Bool
• No instance for (Eq a) arising from a use of ‘==’
Possible fix:
add (Eq a) to the context of
the type signature for:
f :: (a -> a) -> a -> Bool
• In the expression: x == g x
In an equation for ‘f’: f g x = x == g x
Eq
for a custom typeEq
:class
syntax. The functions in the class are given types:instance
syntax:instance Foo [a] -- this is ok
instance Foo [Int] -- this is NOT ok
instance Foo (Maybe a) -- this is ok
instance Foo (Maybe [a]) -- this is NOT ok
instance MyClass (T a b..)
are allowed, where T
is a type constructor and a
, b
, … are different type variables.class Example a where
example :: a
examples :: [a]
examples = [example]
instance Example Int where
example = 1
examples = [0,1,2]
instance Example Bool where
example = True
class Combine a where
combine :: a -> a -> a
combine3 :: a -> a -> a -> a
combine3 x y z = combine x (combine y z)
instance
declarationCheck [a]
instance, we need to add a constraint to the instance declaration:Check a =>
constraint, we get an error: • No instance for (Check a) arising from a use of ‘check’
Possible fix:
add (Check a) to the context of the instance declaration
Check [Int]
instance)Bar
is a subclass of Foo
Eq
, Ord
Eq
is for equality, Ord
is for comparisonsclass (Eq a) => Ord a where
compare :: a -> a -> Ordering
(<), (<=), (>=), (>) :: a -> a -> Bool
max, min :: a -> a -> a
compare x y | x == y = EQ
| x <= y = LT
| otherwise = GT
x <= y = compare x y /= GT
x < y = compare x y == LT
x >= y = compare x y /= LT
x > y = compare x y == GT
max x y | x <= y = y
| otherwise = x
min x y | x <= y = x
| otherwise = y
Eq
and Ord
instances for a simple type<=
operator for Ord
due to the default implementations
data Pair a = MkPair a a
deriving Show
instance Eq a => Eq (Pair a) where
MkPair a b == MkPair c d = a==c && b==d
instance Ord a => Ord (Pair a) where
MkPair a b <= MkPair c d
| a<c = True
| a>c = False
| otherwise = b<=d
*Main> (MkPair 1 2) < (MkPair 2 3)
True
*Main> (MkPair 1 2) > (MkPair 2 3)
False
*Main> compare (MkPair 1 2) (MkPair 2 3)
LT
Num
Num
class contains normal arithmetic operations, plus a couple of weird onesNum
needs Eq
and Show
…Read
: reading values from strings, read :: Read a => String -> a
Show
: printing values as strings, show :: Show a => a -> String
Enum
: for [x..y]
syntaxIntegral
: various integer types: div
, mod
, toInteger
Fractional
: divisionFloating
: floating point operations: exp
, sin
, pi
…deriving
deriving
syntax you’ve already seen is a way to generate automatic instances for a few basic type classes
Read
, Show
and Eq
most notablyshow
prints the value just like it would be in source coderead
reads values that show
produces==
compares all fieldsmap
over:f
is not a type but a type constructor
a
to b
Functor
:instance Functor Tree where
fmap _ Leaf = Leaf
fmap f (Node x l r) = Node (f x) (mapTree f l) (mapTree f r)
Functor
is a class for types that behave like containers[]
:not True = False
not False = True
map f [] = []
map f (x:xs) = f x : map f xs
length [] = 0
length (x:xs) = 1+length xs
_______shared________
| |
f (1+1) ==> if (1+1)>10 then 10 else (1+1)
==> if 2>10 then 10 else 2
==> if False then 10 else 2
==> 2
let ... in ...
where
False
, Just (1+1)
, 0:filter f xs
(\x -> 1+x)
x
to WHNF to know which branch to take:(+)
also force their argumentsnot :: Bool -> Bool
not True = False
not False = True
(||) :: Bool -> Bool -> Bool
True || _ = True
_ || x = x
even x = x == 0 || not (even (x-1))
||
forces its left argument, but not its right argument (pattern matching)even
forces its first argument:
||
forces x==0
x==0
forces x
even 2
==> 2 == 0 || not (even (2-1))
==> False || not (even (2-1))
==> not (even (2-1))
==> not ((2-1) == 0 || not (even ((2-1)-1)))
==> not ( 1 == 0 || not (even ( 1 -1))) -- (sharing)
==> not ( False || not (even (1-1)))
==> not (not (even (1-1)))
==> not (not ((1-1) == 0 || not (even ((1-1)-1))))
==> not (not ( 0 == 0 || not (even ( 0 -1)))) -- (sharing)
==> not (not ( True || not (even (0-1))))
==> not (not True)
==> not False
==> True
even
would not have worked (infinite recursion): head (filter (>100) (map (3^) [0..]))
==> head (filter (>100) (map (3^) (0:[1..])))
==> head (filter (>100) ((3^0) : map (3^) [1..]))
==> head (filter (>100) (1 : map (3^) [1..])) -- (>100) forces 3^0
==> head (filter (>100) (map (3^) (1:[2..])))
==> head (filter (>100) ((3^1) : map (3^) [2..]))
==> head (filter (>100) (3 : map (3^) [2..]))
==> head (filter (>100) (map (3^) [2..]))
-- taking bigger steps now
==> head (filter (>100) (9 : map (3^) [3..]))
==> head (filter (>100) (27 : map (3^) [4..]))
==> head (filter (>100) (81 : map (3^) [5..]))
==> head (filter (>100) (243 : map (3^) [6..]))
==> head (243 : filter (>100) (map (3^) [6..]))
==> 243
Maybe
values, the code tends to become a bit messy:increase :: Eq a => a -> Int -> [(a,Int)] -> Maybe [(a,Int)]
increase key val assocs =
case lookup key assocs
of Nothing -> Nothing
Just x -> if (val < x)
then Nothing
else Just ((key,val) : delete (key,x) assocs)
Nothing
, the whole result is Nothing
?>
(?>) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing ?> _ = Nothing -- if we failed, don't even bother running the next step
Just x ?> f = f x -- otherwise run the next step
increase key val assocs =
lookup key assocs ?>
check ?>
mk
where check x
| x < val = Nothing
| otherwise = Just x
mk x = Just ((key,val) : delete (key,x) assocs)
(?>) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing ?> _ = Nothing -- if we failed, don't even bother running the next step
Just x ?> f = f x -- otherwise run the next step
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:xs) = Just x
safeTail :: [a] -> Maybe [a]
safeTail [] = Nothing
safeTail (x:xs) = Just xs
safeThird xs = safeTail xs ?> safeTail ?> safeHead
safeNth 0 xs = safeHead xs
safeNth n xs = safeTail xs ?> safeNth (n-1)
Logger
represents a value plus a list of log messages (from the computation that produced the value)-- Logger definition
data Logger a = Logger [String] a deriving Show
getVal (Logger _ a) = a
getLog (Logger s _) = s
-- Primitive operations:
nomsg x = Logger [] x -- a value, no message
annotate s x = Logger [s] x -- a value and a message
msg s = Logger [s] () -- just a message
(#>) :: Logger a -> (a -> Logger b) -> Logger b
Logger la a #> f = let Logger lb b = f a -- feed value to next step
in Logger (la++lb) b -- bundle result with all messages
-- compute the expression 2*(x^2+1)
compute x =
annotate "^2" (x*x)
#>
\x -> annotate "+1" (x+1)
#>
\x -> annotate "*2" (x*2)
filter
:data Logger a = Logger [String] a deriving Show
nomsg x = Logger [] x -- a value, no message
annotate s x = Logger [s] x -- a value and a message
msg s = Logger [s] () -- just a message
(#>) :: Logger a -> (a -> Logger b) -> Logger b
Logger la a #> f = let Logger lb b = f a -- feed value to next step
in Logger (la++lb) b -- bundle result with all messages
-- sometimes you don't need the previous value:
(##>) :: Logger a -> Logger b -> Logger b
Logger la _ ##> Logger lb b = Logger (la++lb) b
filterLog :: (Eq a, Show a) => (a -> Bool) -> [a] -> Logger [a]
filterLog f [] = nomsg []
filterLog f (x:xs)
| f x = msg ("keeping "++show x) ##> filterLog f xs #> (\xs' -> nomsg (x:xs'))
| otherwise = msg ("dropping "++show x) ##> filterLog f xs
data Tree a = Leaf | Node a (Tree a) (Tree a)
number tree = t
where (t,i) = number' 0 tree
number' :: Int -> Tree a -> (Tree Int, Int)
number' i Leaf = (Leaf,i)
number' i (Node _ l r) = (Node i' numberedL numberedR, i'')
where (numberedL, i') = number' i l
(numberedR, i'') = number' (i'+1) r
number (Node 0 (Node 0 (Node 0 Leaf Leaf) Leaf) (Node 0 (Node 0 Leaf Leaf) (Node 0 Leaf Leaf)))
==> Node 2 (Node 1 (Node 0 Leaf Leaf) Leaf) (Node 4 (Node 3 Leaf Leaf) (Node 5 Leaf Leaf))
Int
Int -> (a, Int)
a
would be Tree Int
in our exampleCounter a
for this purposedata Counter a = Counter (Int -> (a,Int))
runCounter :: Counter a -> Int -> (a,Int)
runCounter (Counter f) s = f s
-- Get the current state, increment the state by one
getAndIncrement :: Counter Int
getAndIncrement = Counter (\i -> (i,i+1))
-- Produce the given value, don't change the state
noChange :: a -> Counter a
noChange x = Counter (\i -> (x,i))
chain :: Counter a -> (a -> Counter b) -> Counter b
chain op f = Counter g
where g i = let (value, new) = runCounter op i
op2 = f value
in runCounter op2 new
-- query the counter two times
example = getAndIncrement
`chain`
(\val1 -> getAndIncrement `chain` (\val2 -> noChange (val1, val2)))
chain
:number :: Tree a -> Tree Int
number tree = t
where (t,_) = runCounter (number' tree) 0
number' :: Tree a -> Counter (Tree Int)
number' Leaf = noChange Leaf
number' (Node _ l r) =
number' l
`chain`
(\numberedL -> getAndIncrement
`chain`
(\i -> number' r
`chain`
(\numberedR -> noChange (Node i numberedL numberedR))))
(?>) :: Maybe a -> (a -> Maybe b) -> Maybe b
(#>) :: Logger a -> (a -> Logger b) -> Logger b
chain :: Counter a -> (a -> Counter b) -> Counter b
map
and Functor
, there is a type class that captures this pattern:Monad
too:Functor
was about a generic map
operation:Monad
is just about a generic chaining operation:
m a
) and do some further computation with its result (a -> m b
).Maybe
:Monad
instance for Maybe
:instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= _ = Nothing
(Just _) >> k = k
Nothing >> _ = Nothing
return = Just
fail _ = Nothing
increase
example rewritten with monad operations?>
to >>=
, Just
to return
and Nothing
to fail
do
do
: continueddo
notation:do
is just a nicer way to write monad operationsdo
: continueddo
examplessafeNth
using do notation:safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:xs) = Just x
safeTail :: [a] -> Maybe [a]
safeTail [] = Nothing
safeTail (x:xs) = Just xs
safeNth 0 xs = safeHead xs
safeNth n xs = do t <- safeTail xs
safeNth (n-1) t
increase
one more time, with do notation:increase key val assocs = do
val <- lookup key assocs
check val
return ((key,val) : delete (key,x) assocs)
where check x
| val < x = fail ""
| otherwise = return x
Monad
instance for Logger
filterLog
is with do notationdata Logger a = Logger [String] a deriving Show
instance Monad Logger where
return x = Logger [] x
Logger la a >>= f = Logger (la++lb) b
where Logger lb b = f a
msg s = Logger [s] ()
compute x = do
a <- annotate "^2" (x*x)
b <- annotate "+1" (a+1)
annotate "*2" (b*2)
filterLog :: (Eq a, Show a) => (a -> Bool) -> [a] -> Logger [a]
filterLog f [] = return []
filterLog f (x:xs)
| f x = do msg ("keeping "++show x)
xs' <- filterLog f xs
return (x:xs')
| otherwise = do msg ("dropping "++show x)
filterLog f xs
State
monad is a generalized version of our Counter
typedata State s a = State (s -> (a,s))
runState (State f) s = f s
put :: s -> State s ()
put state = State (\_state -> ((),state))
get :: State s s
get = State (\state -> (state,state))
modify :: (s -> s) -> State s ()
modify f = State (\state -> ((), f state))
instance Monad (State s) where
return x = State (\s -> (x,s))
op >>= f = State h
where h state0 = let (val,state1) = runState op state0
op2 = f val
in runState op2 state1
State s
is a type constructor because the second argument is missingState
represents computation with one global variableState
parensMatch xs = v
where (v,_) = runState (matcher xs) 0
matcher :: String -> State Int Bool
matcher [] = do s <- get
return (s==0)
matcher (c:cs) = do case c of '(' -> modify (+1)
')' -> modify (-1)
_ -> return ()
s <- get
if (s<0) then return False else matcher cs
mapM
when :: Monad m => Bool -> m () -> m () -- conditional operation
unless :: Monad m => Bool -> m () -> m () -- same but flipped
replicateM :: Monad m => Int -> m a -> m [a] -- do something many times
replicateM_ :: Monad m => Int -> m a -> m () -- same, but ignore the results
mapM :: Monad m => (a -> m b) -> [a] -> m [b] -- do something on a list's elements
mapM_ :: Monad m => (a -> m b) -> [a] -> m () -- same, but ignore the results
forM :: Monad m => [a] -> (a -> m b) -> m [b] -- mapM but arguments reversed
forM_ :: Monad m => [a] -> (a -> m b) -> m () -- same, but ignore the results
mapM
: continuedfilter
reimplemented using the State
monad:liftM
liftM
makes it easy to write code with pure and monadic parts:liftM
look familiar?Functor
instance for a Monad: fmap = liftM
!Monad
instance for []
) represents computations with multiple return valuesk
findSum :: [Int] -> Int -> [(Int,Int)]
findSum xs k = do a <- xs
b <- xs
if (a+b==k) then [(a,b)] else []
substrings :: String -> [String]
substrings xs = do i <- [0..length xs - 1]
let maxlen = length xs - i
j <- [1..maxlen]
return $ take j $ drop i $ xs
palindromesIn :: String -> [String]
palindromesIn xs = do s <- substrings xs
if (s==reverse s) then return s else fail ""
longestPalindrome xs = head . sortBy f $ palindromesIn xs
where f s s' = compare (length s') (length s) -- longer is smaller
instance Monad [] where
return x = [x] -- an operation that produces one value
lis >>= f = concat (map f lis) -- compute f for all values, combine the results
IO
is a monadIO
is magical. You probably won’t learn anything by reading it.State
and Maybe
mapM
and friends) with IOMonad
type class is a way to represent different ways of executing recipes
Maybe
)M
is a monad, values of type M a
are operations that produce a result of type a
mapM
etc)
State
operation is easier than deciphering a complicated recursion with state#lambda
on IRCNet#haskell
on freenodenewtype
, type
: LYAH