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