Magical Monoids

Ceci n’est pas une pile.
3 min readApr 23, 2019

--

Recently something caught my eye while hacking on a Haskell
project. Pay attention to the mempty in the code fragment below:

readTextFileUtf8 :: FilePath -> IO (Either Utf8Error T.Text)
readTextFileUtf8 filename = {- ... -}
-- | Return "" in case of errors
lenientReadTextFileUtf8 :: FilePath -> IO T.Text
lenientReadTextFileUtf8 filename = do
result <- readTextFileUtf8 filename
pure $ either (\_ -> mempty) id result

I went about my favourite pastime did some code-golfing on lenientReadTextFileUtf8 but inadvertently made a mistake while refactoring which didn’t result in a type error:

lenientReadTextFileUtf8' :: FilePath -> IO T.Text
lenientReadTextFileUtf8' filename
= either mempty pure =<< readTextFileUtf8 filename

Can you spot the “mistake”?

How Haskellers look when stuff compiles that wasn’t supposed to

I should have written

... = either (\_ -> pure mempty) pure =<< ...

as in my mind I expected mempty to be (“” :: Text) and to my surprise the code actually even worked in the way I wanted it to!

Haskell developer’s first reaction to unknown type sorcery

So it was time to go deeper and investigate. After reading up on the base documentation I quickly found out that there’s two peculiar instances(1) defined for Monoid which enable this type magic:

instance Monoid b => Monoid (a -> b) where
mempty = \_ -> mempty
instance Monoid a => Monoid (IO a) where
mempty = pure mempty

In other words, mempty can be specialised into an infinite number of functions according to the repeated application of the definitions above:

mempty :: IO Text
mempty :: IO (IO (IO Text))
mempty :: IO (a -> IO (b -> Text))
mempty :: a -> Text
mempty :: a -> b -> Text
mempty :: a -> b -> IO Text
mempty :: a -> b -> IO (IO Text)

So whenever you’re too lazy to write nested forms of const or pure you can just let the Haskell compiler magically generate those forms on the fly for you via the magic of type inference!

But there’s more! Check out the associated Semigroup instances

instance Semigroup b => Semigroup (a -> b) where
f <> g = \x -> f x <> g x

instance Semigroup a => Semigroup (IO a) where
(<>) = liftA2 (<>)

With those you can now write stuff like

>>> (take 3 <> const "oi" <> drop 4) "Monads are cool!"
"Monoids are cool!"

or

readTextFileUtf8OrLatin1 :: FilePath -> IO (Either String T.Text)
readTextFileUtf8OrLatin1 = readTextFileUtf8 <> readTextFileLatin1

which tries to read a file in UTF8 format and if that fails falls back to reading as Latin1. Aren’t Monoids super awesome? Do you know of other cool Monoid magical tricks?

Footnotes:

(1) Wouldn’t it be even cooler if this worked for every Applicative?

instance (Applicative f, Semigroup a) => Semigroup (f a) where
(<>) = liftA2 (<>)
instance (Applicative f, Monoid a) => Monoid (f a) where
mempty = pure mempty

Update: On Reddit phadej and others pointed out there’s the Ap newtype wrapper defined in the latest base release which allows just that! Its definition is

newtype Ap f a = Ap { getAp :: f a }instance (Applicative f, Semigroup a) => Semigroup (Ap f a) where
(Ap x) <> (Ap y) = Ap $ liftA2 (<>) x y
instance (Applicative f, Monoid a) => Monoid (Ap f a) where
mempty = Ap $ pure mempty

--

--