Proposed BFPG lightning talk. 25 slides, 5 minutes, 12 seconds per slide. No problem!
September 2014
Motivation: we want to write something as clear as this, but this is O(n^2).
reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
Get to O(n) using an accumulator, but this is less clear.
reverse :: [a] -> [a]
reverse xs = go xs []
where
go :: [a] -> [a] -> [a]
go [] acc = acc
go (x:xs) acc = go xs (x:acc)
Let's eliminate the accumulator parameter.
reverse :: [a] -> [a]
reverse xs = go xs []
where
go :: [a] -> [a] -> [a]
go [] = \acc -> acc
go (x:xs) = \acc -> go xs (x:acc)
And again.
reverse :: [a] -> [a]
reverse xs = go xs []
where
go :: [a] -> [a] -> [a]
go [] = id
go (x:xs) = go xs . (x:)
Now compare.
reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
reverse :: [a] -> [a]
reverse xs = go xs []
where
go :: [a] -> [a] -> [a]
go [] = id
go (x:xs) = go xs . (x:)
Another example, also O(n^2).
Tree a = Leaf | Branch (Tree a) a (Tree a)
flatten :: Tree a -> [a]
flatten Leaf = []
flatten (Branch l x r) = flatten l ++ [x] ++ flatten r
Use an accumulator to get to O(n), but this is less clear.
Tree a = Leaf | Branch (Tree a) a (Tree a)
flatten :: Tree a -> [a]
flatten t = go t []
where
go :: Tree a -> [a] -> [a]
go Leaf acc = acc
go (Branch l x r) acc = go l (x : go r acc)
Rearrange.
Tree a = Leaf | Branch (Tree a) a (Tree a)
flatten :: Tree a -> [a]
flatten t = go t []
where
go :: Tree a -> [a] -> [a]
go Leaf = id
go (Branch l x r) = go l . (x:) . go r
And compare.
flatten :: Tree a -> [a]
flatten Leaf = []
flatten (Branch l x r) = flatten l ++ [x] ++ flatten r
flatten :: Tree a -> [a]
flatten t = go t []
where
go :: Tree a -> [a] -> [a]
go Leaf = id
go (Branch l x r) = go l . (x:) . go r
The common pattern.
type DList a = [a] -> [a]
-- empty difference list, O(1).
id :: DList a
-- difference list concatenation, amortised O(1) if used ephemerally.
(.) :: DList a -> DList a -> DList a
-- difference list to ordinary list, amortised O(1) if used ephemerally.
($[]) :: DList a -> [a]
-- singleton difference list, O(1).
(:) : a -> DList a
-- ordinary list to difference list, O(n).
(++) :: [a] -> DList a
Not magic.
type Queue a = DList a
put :: Queue a -> a -> Queue a
put q x = q . (x:)
-- No!
take :: Queue a -> Maybe (a, Queue a)
take q = case q [] of
[] -> Nothing
x:xs -> Just (x, (xs++))
And now for the twist.
reverse :: [a] -> [a]
reverse xs = go xs []
where
go :: [a] -> [a] -> [a]
go [] = id
go (x:xs) = go xs . (x:)
Abstract over the list-specific parts.
reverse :: (a -> [a] -> [a]) -> [a] -> [a] -> [a]
reverse f z xs = go xs z
where
go :: [a] -> [a] -> [a]
go [] = id
go (x:xs) = go xs . f x
Generalise the type. What do we get?
reverse :: (a -> b -> b) -> b -> [a] -> b
reverse f z xs = go xs z
where
go :: [a] -> b -> b
go [] = id
go (x:xs) = go xs . f x
A slight variation on the Prelude function.
foldl :: (a -> b -> b) -> b -> [a] -> b
foldl f z xs = go xs z
where
go :: [a] -> b -> b
go [] = id
go (x:xs) = go xs . f x
Just swap the function composition.
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z xs = go xs z
where
go :: [a] -> b -> b
go [] = id
go (x:xs) = f x . go xs
Monoids. They're everywhere.
reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
reverse :: [a] -> [a]
reverse xs = go xs []
where
go :: [a] -> [a] -> [a]
go [] = id
go (x:xs) = go xs . (x:)
Monoids. They're everywhere.
instance Monoid [a] where
mempty = []
mappend = (++)
newtype Endo b = Endo { appEndo :: b -> b }
instance Monoid (Endo a) where
mempty = Endo id
Endo f `mappend` Endo g = Endo (f . g)
Rewrite inefficient reverse
and flatten
using Monoid
and Reducer
.
reverse :: [a] -> [a]
reverse [] = mempty
reverse (x:xs) = reverse xs <> unit x
flatten :: Tree a -> [a]
flatten Leaf = mempty
flatten (Branch l x r) = flatten l <> unit x <> flatten r
We get the efficient versions just by switching types (sans wrappers).
reverse :: [a] -> Endo [a]
reverse [] = mempty
reverse (x:xs) = reverse xs <> unit x
flatten :: Tree a -> Endo [a]
flatten Leaf = mempty
flatten (Branch l x r) = flatten l <> unit x <> flatten r
Let's write foldr
using Monoid
.
foldMap :: Monoid m => (a -> m) -> [a] -> m
foldMap f = mconcat . map f
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z xs = appEndo (foldMap (Endo . f) xs) z
We need the Dual
of Endo
to flip the function composition for foldl
.
newtype Dual a = Dual { getDual :: a }
instance Monoid a => Monoid (Dual a) where
mempty = Dual mempty
Dual x `mappend` Dual y = Dual (y `mappend` x)
foldl :: (a -> b -> b) -> b -> [a] -> b
foldl f z xs = appEndo (getDual $ foldMap (Dual . Endo . f) xs) z
Tree
has a foldMap, too!
foldMap :: Monoid m => (a -> m) -> Tree a -> m
foldMap f Leaf = mempty
foldMap f (Branch l x r) = foldMap f l <> f x <> foldMap f r
Put foldMap
in a type class, get foldr
and foldl
for free.
class Foldable t where
foldMap :: Monoid m => (a -> m) -> t a -> m
A better flatten
.
flatten :: Foldable t => t a -> [a]
flatten = foldr (:) []