Motivation

I have some code which is presently run for its side effects, but I’d like it data too. It’s analogous to extending writeFile to return the size of the contents:

writeFileAndCount :: FilePath -> String -> IO Int			
writeFileAndCount path contents = do					
                                     writeFile path contents		
                                     return $ length contents		

All well and good! Now we could just have two functions, one which returns IO () and the other which returns IO Int, but it seems a shame to pollute the namespace. Instead it would be nice if we could say e.g.:

ghci> writeFile' "foo.txt" "Hello" :: IO ()
ghci> writeFile' "foo.txt" "Hello" :: IO Int
5

Happily we can!

A mathematical analogy

You’ve probably already seen code whose return type changes to match the context. For example in Haskell’s maths libraries many functions will return either Float or Double. Here’s sqrt:

ghci> (sqrt 2) :: Float
1.4142135
ghci> (sqrt 2) :: Double
1.4142135623730951

To see how this works, look at the type of sqrt:

ghci> :t sqrt
sqrt :: Floating a => a -> a

There’s no mention of Double or Float there. Instead, we see that sqrt will work with any instance of the Floating typeclass.

Under the covers we’d expect different instances of sqrt: one Double -> Double, another Float -> Float. Having inferred the relevant type, the compiler will then pick the particular instance we need.

Conceptually we might have:

sqrtD :: Double -> Double
sqrtF :: Float  -> Float
sqrt  :: (Floating a) => a -> a

Note that all of these functions don’t change the type: we can’t implicitly change e.g. a Float to a Double:

ghci> (sqrt (2 :: Float)) :: Double					
									
<interactive>:5:8:							
    Couldn't match expected type ‘Double’ with actual type ‘Float’	
    In the first argument of ‘sqrt’, namely ‘(2 :: Float)’		
    In the expression: (sqrt (2 :: Float)) :: Double			
    In an equation for ‘it’: it = (sqrt (2 :: Float)) :: Double		

This is because the signature has just one degree-of-freedom:

sqrt :: a -> a

rather than

sqrt :: a -> b

A polymorphic wrapper

Having seen that sqrt can choose different code in different contexts, let’s try to write a combinator which either passes a value unchanged, or converts it to ().

By analogy with sqrt consider combining:

toId   :: a -> a
toUnit :: a -> ()

Although the first equation both accepts and returns the same type, the second doesn’t. So it makes sense to invent a type class with two parameters. On a technical level, this means we’ll need the MultiParamTypeClasses1 GHC extension.

Here’s a suitable type class:

class OrUnit b a where
  orUnit :: a -> b

We also need a couple of instances:

instance OrUnit () a where
 orUnit a = ()

instance OrUnit a a where
 orUnit a = a

In the first instance above, the () is a concrete type rather than a variable, so we’ll also need the FlexibleInstances2 extension.

Given this, we can write things like this:

ghci> orUnit 'a' :: Char
'a'
ghci> orUnit 'a' :: ()
()

or indeed our original goal of writeFile':

writeFile' :: (OrUnit a Int) => FilePath -> String -> IO a
writeFile' path contents = liftM orUnit
                             $ writeFileAndCount path contents

We need liftM to lift orUnit into the IO Monad.

Problems

Although we’ve met our original goal of adding optional return data from a function whilst keeping compatibility with old code, it isn’t perfect.

We saw above that we need GHC extensions to compile the module. Sadly we also need to enable extensions when using it:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts  #-}

So, it’s doesn’t give a completely backwards-compatible way to extend the original API.

Also the extra flexibility we enjoy seems to force us to specify explicit types more often. Perhaps other GHC extensions would help here.

The code

You can grab the code from GitHub3. It’s just for fun, and consequently I’ve not uploaded it to hackage.