lecture4 ::
forall (m :: *) . Monoid m
⇒ [m] → m
lecture4 =
foldr mappend mempty
Types really can help!
What can go wrong?..
But types can't help you much if you don't use their true power...
newtype Hash = MkHash String
class Hashable a where
hash :: a -> Hash
hashPair :: Int -> Hash
hashPair n = hash (n, n)
hashPair :: Int -> Hash
hashPair n = hash n -- oops, this is valid definition!
Phantom types
newtype Hash a = MkHash String -- `a` is a phantom type, not present in constructor
class Hashable a where
hash :: a -> Hash a
hashPair :: Int -> Hash (Int, Int)
hashPair n = hash (n, n)
hashPair :: Int -> Hash (Int, Int)
hashPair n = hash n -- This is no longer a valid definition!
Hash.hs:7:14: error:
• Couldn't match type ‘Int’ with ‘(Int, Int)’
Expected type: Hash (Int, Int)
Actual type: Hash Int
• In the expression: hash n
In an equation for ‘hashPair’: hashPair n = hash n
Limitations of such approach?
Some real life examples
Crypto
newtype Signature a = Signature ByteString
sign :: Binary t => SecretKey -> t -> Signature t
path package: well-types paths
data Abs -- absolute path
data Rel -- relative path
data Dir -- directory
data File -- file
newtype Path b t = Path FilePath
-- appending paths
(</>) :: Path b Dir -> Path Rel t -> Path b t
o-clock package: time-safe units
data Second
data Microsecond -- rough approximation
newtype Time unit = Time (Ratio Natural)
So you should understand types well enough to embrace their true power
∀ or ¬∀?
Can anybody completely explain the forall keyword in clear, plain English?
No.
Who said OOP?
showAll :: Show a => [a] -> [String]
showAll = map show
ghci> showAll [2, 3]
["2", "3"]
ghci> showAll [1, "ABA"] -- ??
No instance for (Num [Char]) arising from the literal ‘1’
In the expression: 1
In the first argument of ‘showAll’, namely ‘[1, "ABA"]’
In the expression: showAll [1, "ABA"]
a type variable is fixed
ghci> [False, "ABA"] -- fails
Couldn't match expected type ‘Bool’ with actual type ‘[Char]’
In the expression: "ABA"
In the expression: [False, "ABA"]
In an equation for ‘it’: it = [False, "ABA"]
More types
ghci> reverse [2, 1, 3]
[3,1,2]
ghci> reverse [True, False]
[False,True]
applyTwo :: ([Int], [Bool])
applyTwo = let call f = (f [2, 1, 3], f [True, False]) in call reverse -- (*)
-- but we get:
(*):29:
No instance for (Num Bool) arising from the literal ‘2’
In the expression: 2
In the first argument of ‘f’, namely ‘[2, 1, 3]’
In the expression: f [2, 1, 3]
(*):15:
Couldn't match type ‘Int’ with ‘Bool’
Expected type: ([Int], [Bool])
Actual type: ([Int], [Int])
In the expression: call reverse
In the expression: let call f = (f ..., f ...) in call reverse
(*):20:
Couldn't match type ‘Bool’ with ‘Int’
Expected type: [Bool] -> [Int]
Actual type: [Int] -> [Int]
In the first argument of ‘call’, namely ‘reverse’
In the expression: call reverse
-- and we want
gchi> applyTwo
([3,1,2], [False,True])
forall
id :: a -> a
id x = x
-- real type with explicit forall
id :: forall a . a -> a
id x = x
Read as:
For every type a this function
can be considered to have type a → a
forall example
ghci> applyTwo id -- works like magic
([2,1,3],[True,False])
ghci> applyTwo reverse
([3,1,2],[False,True])
applyTwo :: (forall a . [a] -> [a]) -> ([Int], [Bool])
applyTwo f = (f [2, 1, 3], f [True, False])
applyTwo :: ([a] -> [a]) -> ([Int], [Bool]) -- doesn't compile
applyTwo f = (f [2, 1, 3], f [True, False])
ghci> (\(a, b) -> (reverse a, reverse b)) ("hello", [2, 1, 3]) -- ??
applyTwo :: forall a . ([a] -> [a]) -> ([Int], [Bool]) -- equivalent form ^
applyTwo f = (f [2, 1, 3], f [True, False])
Instead we should....
Does this work and why?
More generic applyTwo
applyTwo :: ([a] -> [a]) -> ([Int], [Bool])
applyTwo f = (f [2, 1, 3], f [True, False])
Trying to understand why this doesn't work
Let's make it more generic!
applyTwo :: ([a] -> [a]) -> [b] -> [c] -> ([b], [c])
applyTwo f x y = (f x, f y)
• Couldn't match type ‘b’ with ‘a’
‘b’ is a rigid type variable bound by
the type signature for:
applyTwo :: forall a b c. ([a] -> [a]) -> [b] -> [c] -> ([b], [c])
ghci> applyTwo id [2,1,3] [True,False]
([2,1,3],[True,False])
ghci> applyTwo reverse [True,False] "patak"
([False,True],"katap")
applyTwo :: (forall a . [a] -> [a]) -> [b] -> [c] -> ([b], [c])
applyTwo f x y = (f x, f y)
reverse :: forall a . [a] -> [a]
applyTwo :: forall b c . (forall a . [a] -> [a]) -> [b] -> [c] -> ([b], [c])
applyTwo f x y = (f x, f y)
Type of reverse matches better our last implementation
Slide 13: <undefined error>
undefined :: forall a . a -- intersection of all types
undefined = ⊥
What is undefined in Haskell (true language)?
What is undefined in bad languages?
{-# LANGUAGE RankNTypes #-}
Rank 0: Int
Rank 1: forall a . a -> Int
Rank 2: (forall a . a -> Int) -> Int -- could be enabled by Rank2Types
Rank 3: ((forall a . a -> Int) -> Int) -> Int
A function type has rank n + 1 when its argument has rank n.
Int -> Int
Exercises:
Int -> Int -- rank 0
forall a . a -> a
forall a . a -> a -- rank 1
The rank of a type describes the depth at which universal quantifiers appear in a contravariant position, i.e. to the left of a function arrow.
(forall a . a -> a) -> Int
(forall a . a -> a) -> Int -- rank 2
Int -> (forall a . a -> a)
forall a . Int -> a -> a -- rank 1
forall a b . a -> b -> a
forall a b . a -> b -> a -- rank 1
forall a . a -> (forall b . b -> a)
(a -> a) -> (forall b . b -> b) -> (c -> c)
forall a b . a -> b -> a -- rank 1
(a -> a) -> (forall b . b -> b) -> (c -> c) -- rank 2
So you probably have such question:
Where I need forall in real life?
Show everything existed
showAll :: Show a => [a] -> [String]
showAll = map show
showAll :: forall a . Show a => [a] -> [String]
showAll = map show
showAll :: Show a => [forall a . a] -> [String] -- 'a' not in scope for Show
showAll = map show
showAll :: [forall a . Show a => a] -> [String] -- only bottoms
showAll = map show
{-# LANGUAGE ExistentialQuantification #-}
data ShowBox = forall a . Show a => SB a -- existental constructor
showAll :: [ShowBox] -> [String]
showAll = map (\(SB a) -> show a)
ghci> showAll [SB (), SB 1, SB True] -- again, this magic works
["()","1","True"]
showAll :: forall a . [Show a => a] -> [String] -- ImpredicativeTypes (broken)
showAll = map show
You can simulate OOP-style of programming now!
Okay, I promised real life example...
ghci> incShow "3" -- why does this work at all?
"4"
ghci> incShow "3.0"
"*** Exception: Prelude.read: no parse
How about this?
We want to deserialiaze data, perform some transformations, and then serialize back. Oh, and we want single function that will work for every type automatically.
-- incShow :: ???
incShow = show . (+1) . read
ghci> :t read -- our simple 'deserialize' function
read :: Read a => String -> a
ghci> :t show -- our simple 'serialize' function
show :: Show a => a -> String
incShow :: String -> String
incShow = show . (+1) . read
Type variable 'a' is not visible in type! How can we specify type?...
How to solve this problem in OOP world, btw?
Prepare for long story...
What a wonderful type system
-- v1.0.0: also compiles
prepend2 :: Int -> [Int] -> [Int]
prepend2 x xs = pair ++ xs
where pair = [x, x]
-- v3.0.0: doesn't compile!
prepend2 :: a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where pair :: [a]
pair = [x, x]
-- v0.0.0: this compiles
prepend2 :: Int -> [Int] -> [Int]
prepend2 x xs = pairFun x ++ xs
where pairFun y = [y, y]
-- v2.0.0: compiles or not?
prepend2 :: a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where pair = [x, x]
-- v2.1.0: everything works!
prepend2 :: a -> [a] -> [a]
prepend2 x xs = pairFun x ++ xs
where pairFun :: a -> [a]
pairFun y = [y, y]
Closer look at compiler error
-- v3.0.0: doesn't compile!
prepend2 :: a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where pair :: [a]
pair = [x, x]
Forall.hs:18:17: error:
• Couldn't match expected type ‘a1’ with actual type ‘a’
‘a’ is a rigid type variable bound by
the type signature for:
prepend2 :: forall a. a -> [a] -> [a]
at Forall.hs:15:1-27
‘a1’ is a rigid type variable bound by
the type signature for:
pair :: forall a1. [a1]
at Forall.hs:17:9-19
• In the expression: x
In the expression: [x, x]
In an equation for ‘pair’: pair = [x, x]
• Relevant bindings include
pair :: [a1] (bound at Forall.hs:18:9)
xs :: [a] (bound at Forall.hs:16:12)
x :: a (bound at Forall.hs:16:10)
prepend2 :: a -> [a] -> [a] (bound at Forall.hs:16:1)
|
18 | pair = [x, x]
| ^
We need some scoping...
prepend2 :: a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where
pair :: [a]
pair = [x, x]
id :: a -> a
This definition just...
...contains implicit forall!
id :: forall . a -> a
Inner forall shadows top-level forall!
prepend2 :: forall a . a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where
pair :: forall a . [a]
pair = [x, x]
Same for functions inside where block!
-- it's basically the same reason why this doesn't compile
prepend2 :: forall a b . b -> [a] -> [a]
prepend2 x xs = [x, x] ++ xs
-XScopedTypeVariables
{-# LANGUAGE ScopedTypeVariables #-}
prepend2 :: forall a . a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where
pair :: [a] -- uses same type variable 'a'
pair = [x, x]
-XScopedTypeVariables language extension allows to use type variables from top-level function signature inside this function body (including where block). Works only with forall keyword!
-XTypeApplications
ghci> read "3" :: Int
3
ghci> read "3.0" :: Double
3.0
-- in some artifical syntax...
read :: (a :: Type) -> (_ :: String) -> (x :: a)
How many arguments read has?
read :: forall a . Read a => String -> a
ghci> :t read
read :: Read a => String -> a
ghci> read @Int "3"
3
ghci> read @Double "3.0"
3.0
ghci> :t read @Int
read @Int :: String -> Int
ghci> read "3" :: Int -- so, here we basically just pass Int type
3
ghci> read "3.0" :: Double
3.0
-XTypeApplications: visible type application
Now we can pass types as arguments to functions!
-XAllowAmbiguousTypes
-- do you see problems with this code?
class Size a where
size :: Int
• Could not deduce (Size a)
from the context: Size a
bound by the type signature for:
size :: forall a. Size a => Int
{-# LANGUAGE AllowAmbiguousTypes #-}
class Size a where size :: Int
instance Size Int where size = 8
instance Size Double where size = 16
ghci> size -- this all looks great, but how can I call 'size' function??????
• Ambiguous type variable ‘a0’ arising from a use of ‘size’
prevents the constraint ‘(Size a0)’ from being solved.
Probable fix: use a type annotation to specify what ‘a0’ should be.
These potential instances exist:
instance [safe] Size Double -- Defined at Forall.hs:12:10
instance [safe] Size Int -- Defined at Forall.hs:9:10
ghci> size @Double -- use -XTypeApplications
16
-- simple plain haskell
class Size a where
size :: a -> Int
Show must go on
-- v0.0.0: this doesn't work obviously
incShow :: String -> String
incShow = show . (+1) . read
Result
ghci> incShow @Int "3"
"4"
ghci> incShow @Double "3.2"
"4.2"
ghci> incShow @Rational "3 % 5"
"8 % 5"
-- v0.0.1: why this doesn't work?
incShow :: (Read a, Show a, Num a) => String -> String
incShow = show . (+1) . read
{-# LANGUAGE AllowAmbiguousTypes #-}
-- v0.0.2: why still doesn't work??
incShow :: (Read a, Show a, Num a) => String -> String
incShow = show . (+1) . read
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeApplications #-}
-- v0.0.3: why still this doesn't work???
incShow :: (Read a, Show a, Num a) => String -> String
incShow = show . (+1) . read @a
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
-- v1.0.0: this works!
incShow :: forall a . (Read a, Show a, Num a) => String -> String
incShow = show . (+1) . read @a
New best friends
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE AllowAmbiguosTypes #-}
Kinds
Types of types
ghci> :kind Int
Int :: *
ghci> :kind Char
Char :: *
ghci> :kind Maybe
Maybe :: * -> *
ghci> :kind Maybe String
Maybe :: *
ghci> :kind (Maybe Maybe) -- ??
data MapTree k v
= Leaf
| Node k v (MapTree k v) (MapTree k v)
MapTree :: * -> * -> *
MapTree a :: * -> *
MapTree String v :: *
In FP everything is a function. So you think of types as of functions. And types also can be partially applied.
List type
ghci> :info []
data [] a = [] | a : [a] -- Defined in ‘GHC.Types’
...
ghci> :kind []
[] :: * -> * -- kind of list
ghci> :kind [] Int
[] Int :: * -- `[] a` is the same as [a]
ghci> :kind [String]
[String] :: *
Arrow type
ghci> :info (->)
data (->) t1 t2 -- Defined in ‘GHC.Prim’
infixr 0 `(->)`
...
ghci> :k (->)
(->) :: * -> * -> * -- kind of function
ghci> :k (->) Int
(->) Int :: * -> *
-- ((->) Int) is the same as (Int -> )
Why care about kinds?
ghci> data Computable a f = Computation (f a) a
ghci> :kind Computable
Computable :: * -> (* -> *) -> *
Your types can be parametrized by higher-kinded types!
ghci> :kind Computable String
Computable String :: (* -> *) -> *
ghci> :kind Computable Int Maybe
Computable Int Maybe :: *
ghci> :kind Computable (Maybe String) ((->) Int)
Computable (Maybe String) ((->) Int) :: *
⇒ new level of abstraction
newtype IntComputation f = MkIntComp (f Int) -- option 1: new data type
type IntComputation f = Computable Int f -- option 2: specialization
ghci> :k IntComputation -- in both cases
IntComputation :: (* -> *) -> *
Not convinced?
You must understand compiler errors
Kind polymorphism
ghci> :set -XKindSignatures
ghci> :set -XPolyKinds
ghci> data PolyComputable (f :: k -> *) (a :: k) = PolyComputation (f a)
ghci> :kind PolyComputable
PolyComputable :: (k -> *) -> k -> *
Parametric kinds (remeber: types are functions)
ghci> :k PolyComputable Maybe
PolyComputable Maybe :: * -> *
ghci> :k PolyComputable IntComputation
PolyComputable IntComputation :: (* -> *) -> *
Still don't see how many levels of abstractions you can have and how awesome and parametric code you can write? Well...
Higher-kinded classes
class Box b where -- b :: * -> *
box :: a -> b a
unbox :: b a -> a
Not possible without types of higher kind (but possible in Haskell)
instance Box Maybe where -- "instance Box (Maybe a) where" is compilation error
box = Just -- η-reduce from: box a = Just a
unbox (Just a) = a
unbox Nothing = error "Can't unbox from empty context"
instance Box [] where
box :: a -> [a]
box x = [x]
unbox :: [a] -> a
unbox [x] = x
boxCall :: Box m => (a -> b) -> m a -> m b
boxCall f b = box $ f $ unbox b
ghci> boxCall (+1) (Just 4)
Just 5
What kind of Haskell is it???
class Num a where ...
ghci> :kind Num
??
Hmmm, what if...
ghci> :kind Num
Num :: * -> Constraint
{-# LANGUAGE ConstraintKinds #-}
type MyConstraints a = (Read a, Num a, Show a)
foo :: MyConstraints a => String -> a -> a
You can create aliases for constraints!
ghci> :set -XRankNTypes
ghci> type ConstraintEndomorphism p a = p a => a -> a
ghci> :kind ConstraintEndomorphism
ConstraintEndomorphism :: (* -> Constraint) -> * -> *
And much more
Constraint aliases in real life
Expands to something like this...
Big alias
Math through types
OOP vs. Math
-- not in Haskell standard libraries
class Magma m where
meld :: m -> m -> m
In OOP you invent programming patterns.
In FP you discover programming patterns.
1. (++)
2. max/min
3. x ○ y = xy + x^2 - y
-- actually in 'base'
class Semigroup m where
(<>) :: m -> m -> m
Associativity law for Semigroup:
1. (x <> y) <> z ≡ x <> (y <> z)
1. (++)
2. max/min
Semigroups
Monoids
-- also in 'base' but...
class Semigroup m => Monoid m where
mempty :: m
Identity laws for Monoid:
2. x <> mempty ≡ x
3. mempty <> x ≡ x
1. (++)
Semigroup list instance
class Semigroup a where
(<>) :: a -> a -> a
sconcat :: NonEmpty a -> a
stimes :: Integral b => b -> a -> a
instance Semigroup [a] where
(<>) = (++)
ghci> [1..5] <> [2,4..10]
[1,2,3,4,5,2,4,6,8,10]
>>> 3 * list(range(1,6))
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
Python3
ghci> concat (replicate 3 [1..5])
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
Haskell
No!
ghci> 3 `stimes` [1..5]
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
Semigroup numeric instances
instance Semigroup Int where
-- Which one to choose?
1. (<>) = (+)
2. (<>) = (*)
We can combine numbers as well. But how?
newtype Sum a = Sum { getSum :: a }
newtype Product a = Product { getProduct :: a }
instance Num a => Semigroup (Sum a) where
Sum x <> Sum y = Sum (x + y)
instance Num a => Semigroup (Product a) where
Product x <> Product y = Product (x * y)
Haskell way:
emulate multiple instances for one type with multiple newtypes
ghci> 3 <> 5 :: Sum Int
Sum { getSum = 8 }
ghci> 3 <> 5 :: Product Int
Product { getProduct = 15 }
More Semigroup instances
newtype Max a = Max { getMax :: a } -- max
newtype Min a = Min { getMin :: a } -- min
newtype Any = Any { getAny :: Bool } -- ||
newtype All = All { getAll :: Bool } -- &&
newtype First a = First { getFirst :: a } -- fst
newtype Last a = Last { getLast :: a } -- snd
Use same approach for different situations
ghci> Max 3 <> Max 10 <> Max 2
Max { getMax = 10 }
ghci> Min 3 <> Min 10 <> Min 2
Min { getMin = 2 }
ghci> Any True <> Any False <> Any True
Any { getAny = True }
ghci> All True <> All False <> All True
All { getAll = False }
ghci> First 5 <> First 10 <> First 1
First { getFirst = 5 }
ghci> Last 5 <> Last 10 <> Last 1
Last { getLast = 1 }
One <> to rule them all
Welcome Monoid
-- not a descendant of Semigroup for now
class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
instance Monoid [a] where
mempty = []
l1 `mappend` l2 = l1 ++ l2
And all the rest is almost same...
instance (Monoid a, Monoid b) => Monoid (a,b) where
mempty = ( mempty, mempty)
(a1,b1) `mappend` (a2,b2) = (a1 `mappend` a2, b1 `mappend` b2)
Not exactly monoid...
newtype First a = First { getFirst :: a }
newtype Last a = Last { getLast :: a }
What about First and Last? How to write mempty ?
instance Semigroup a => Monoid (Maybe a) where
mempty = Nothing
Nothing `mappend` m = m
m `mappend` Nothing = m
Just m1 `mappend` Just m2 = Just (m1 <> m2)
With Maybe any Semigroup can be a Monoid
Last and First from Data.Monoid module:
newtype First a = First { getFirst :: Maybe a }
newtype Last a = Last { getLast :: Maybe a }
Function is a Monoid
instance Monoid b => Monoid (a -> b) where
mempty _ = mempty
mappend f g x = f x `mappend` g x
I told you, everything is Monoid
ghci> (replicate 3 <> replicate 2) 1
[1, 1, 1, 1, 1]
And what does it mean?
Interesting Ordering of slides...
data Ordering = LT | EQ | GT
instance Monoid Ordering where
mempty = EQ
LT `mappend` _ = LT
EQ `mappend` y = y
GT `mappend` _ = GT
module Data.Ord (comparing, ...) where
comparing :: Ord a => (b -> a) -> b -> b -> Ordering
comparing p x y = compare (p x) (p y)
Why we need such instance?
data ErrorPosition = ErrorPosition
{ path :: String
, line :: Int
, offset :: Int
} deriving (Eq)
-- this implementation uses Monoid instances of (->) and Ordering
instance Ord ErrorPosition where
compare = comparing path <> comparing line <> comparing offset
Lexicographically sort data types
Monoids in real life
Composability is what FP for
Vertical scaling vs. Horizontal scaling
1. Combination of plugins is a plugin
2. Combination of configurations is configuration of the same type
3. Combination of event sources is an event source
4. Combination of clusters is a cluster
5. Combination of parsers is a parser
6. Combination of streams is a stream
7. Combination of functions is a function
and so on...
data Options = Options
{ oRetryCount :: Int
, oHost :: String
, oCharacterCode :: Maybe Char
} deriving (Show, Eq)
Goal: we want to have options. We want to create multiple versions of records and combine them (defaults + CLI + file config)
Use monoid! (full details in blog post)
data PartialOptions = PartialOptions
{ poRetryCount :: Last Int
, poHost :: Last String
, poCharacterCode :: Last (Maybe Char)
} deriving (Show, Eq)
instance Monoid PartialOptions where
mempty = PartialOptions mempty mempty mempty
mappend x y = PartialOptions
{ poRetryCount = poRetryCount x <> poRetryCount y
, poHost = poHost x <> poHost y
, poCharacterCode = poCharacterCode x <> poCharacterCode y
}
And so on...
Holey Monoid
newtype HoleyMonoid m r a = HoleyMonoid { runHM :: (m -> r) -> a }
Monoid instance for the following data type:
ghci> format ("Person's name is " % text % ", age is " % hex) "Dave" 58
"Person's name is Dave, age is 3a"
Allows to write formatting like this ( formatting library)
Beyond Monoid to the stars
Fold
foldr and foldl
foldr :: (a -> b -> b) -> b -> [a] -> b
foldl :: (b -> a -> b) -> b -> [a] -> b
Simple generalization of recursion
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
Generalization of generalization
ghci> foldr (+) 0 [2, 1, 10]
13
ghci> foldr (*) 3 [2, 1, 10]
60
foldr (\s rest -> rest + length s) 0 ["aaa", "bbb", "s"]
foldr (\rest s -> rest + length s) 0 ["aaa", "bbb", "s"]
Which one correct?
Foldable type class
-- | Simplified version of Foldable
class Foldable t where
{-# MINIMAL foldMap | foldr #-}
fold :: Monoid m => t m -> m
foldMap :: Monoid m => (a -> m) -> t a -> m
foldr :: (a -> b -> b) -> b -> t a -> b
Some basic instances
instance Foldable [] where
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr _ z [] = z
foldr f z (x:xs) = x `f` foldr f z xs
instance Foldable Maybe where
foldr :: (a -> b -> b) -> b -> Maybe a -> b
foldr _ z Nothing = z
foldr f z (Just x) = f x z
Reasons to drop Haskell
ghci> length (4, [1,2,3])
???
ghci> foldr (+) 1 (Just 3)
4
ghci> foldr (+) 0 Nothing
0
ghci> length (4, [1,2,3])
1
Grocking foldr
foldr (+) 0 (1:2:3:[]) ≡ 1+2+3+0
Think of folds as replacing constructors, i.e. ':', '[]', with functions/values.
ghci> import Debug.SimpleReflect
ghci> sum [1..5] :: Expr
0 + 1 + 2 + 3 + 4 + 5
ghci> foldr f x [a,b,c]
f a ( f b ( f c x ) )
Monoidal parsing
ghci> balanced "(())"
True
ghci> balanced "(())("
False
ghci> balanced ")(())("
False
We want verify that sequance round brackets is balanced
data B = B Int Int
instance Monoid B where
mempty = B 0 0
mappend (B a b) (B c d)
| b <= c = B (a + c - b) d
| otherwise = B a (d + b - c)
parse :: Char -> B
parse '(' = B 0 1
parse ')' = B 1 0
parse _ = B 0 0
balanced :: String -> Bool
balanced xs = foldMap parse xs == mempty
Exercises & challenges
secondMax :: Ord a => [a] -> Maybe a
ghci> secondMax [2, 1, 3]
Just 2
mconcat :: Monoid m => [m] -> m
foldMap :: (Foldable f, Monoid m) => (a -> m) -> f a -> m
foldr :: Foldable f => (a -> b -> b) -> b -> f a -> b
1. Default implementation of mconcat
2. foldMap using foldr
4* foldr using foldMap
5* second maximum in list using foldr
fold :: (Foldable f, Monoid m) => f m -> m
3. Default implementation of fold
Literature for all lazy people
Haskell Lecture 04: Kinda monoidal types
By Dmitrii Kovanikov
Haskell Lecture 04: Kinda monoidal types
Lecture about forall keyword, RankNTypes, kinds several standard type classes: Semigroup, Monoid, Foldable.
- 975