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 languages| Haskell | 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 -> returnTypearg1Type -> arg2Type -> returnTypea1 -> a2 -> a3 -> pif 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 wherewhere 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. " ++ nameJava:
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 ni 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 = 1f 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 cumbersomeBoolTrue 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, thisIsATypeVariableBool) 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 nk 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 listPrelude> (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
13is 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) xprocess: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
1Prelude> 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..]))
==> 243Bool 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 ShowPrelude> 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 itderiving 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 -> xMaybe 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, …NothingEither: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 breadInt :: 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]Leafa 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
==> 3insert :: 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 ofcase 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 var2returnreturn 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 threedo 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 Xfoo will also be IO XlastOp must be of type Y -> IO Xfoo 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 dodo always means a value of type IO YIO wrapper, but you must return to itIO worldprintDescription :: Int -> IO ()
printDescription n
| even n = putStrLn "even"
| n==3 = putStrLn "three"
| otherwise = print nIO-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 aSystem.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 tsputStrLn :: String -> IO () is a pure function that returns an operationputStrLn x is the same when x is the samemain :: IO ()mainchoice :: 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 Trueopc :: IO a -> (a -> IO b) -> (a -> IO ()) -> IO b
opc open process close = do
resource <- open
result <- process resource
close resource
return resultfirstLineOfFile path = opc (openFile path ReadMode) hGetFile hClose
withFile path op = opc (openFile path ReadMode) op hCloseIORef a is a mutable reference to a value of type anewIORef :: 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 rIORef 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 valuereturn 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 Ints and Doubles?==?(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 okinstance 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 = Trueclass 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, OrdEq 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 = yEq 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
NumNum class contains normal arithmetic operations, plus a couple of weird onesNum needs Eq and Show…Read: reading values from strings, read :: Read a => String -> aShow: printing values as strings, show :: Show a => a -> StringEnum: for [x..y] syntaxIntegral: various integer types: div, mod, toIntegerFractional: divisionFloating: floating point operations: exp, sin, pi…
Haskell 2010 type classes
derivingderiving 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 bFunctor: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
==> 2let ... in ...whereFalse, 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==0x==0 forces xeven 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
==> Trueeven 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 xsdata 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) rnumber (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))IntInt -> (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 bmap 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 _ = Nothingincrease example rewritten with monad operations?> to >>=, Just to return and Nothing to faildodo: 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) tincrease 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 xMonad instance for LoggerfilterLog 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 xsState 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 state1State s is a type constructor because the second argument is missingState represents computation with one global variableStateparensMatch 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 csmapMwhen :: 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 resultsmapM: continuedfilter reimplemented using the State monad:liftMliftM 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 valueskfindSum :: [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 smallerinstance Monad [] where
return x = [x] -- an operation that produces one value
lis >>= f = concat (map f lis) -- compute f for all values, combine the resultsIO is a monadIO is magical. You probably won’t learn anything by reading it.State and MaybemapM 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 amapM etc)
State operation is easier than deciphering a complicated recursion with state#lambda on IRCNet#haskell on freenodenewtype, type: LYAH