
Unit testing IO in Haskell - fractalsea
https://blog.pusher.com/unit-testing-io-in-haskell/
======
gamegoblin
This is a good post in that it illustrates how it's possible to write useful
Haskell that interacts with the real world without weaving IO through the
entire codebase (as many beginners end up doing).

I still find myself preferring to use a dependency injection style when
writing Haskell. The code ends up looking similar, and more flexible, in my
opinion (you can inject different dependencies based on runtime values).

Here is an example using the method described in the article vs. dependency
injection to print out the current time.

Article:

    
    
        class MonadTime m where
            getTime :: m Integer
    
        class MonadPrint m a where
            doPrint :: a -> m ()
    
        instance MonadTime IO where
            getTime = do
                TOD s _ <- getClockTime
                return s
    
        instance Show a => MonadPrint IO a where
            doPrint = print
    
        printTime :: (Monad m, MonadPrint m Integer, MonadTime m) => m ()
        printTime = getTime >>= doPrint
    
        main :: IO ()
        main = printTime
    

Dependency injected:

    
    
        data MonadPrint m a = MonadPrint { doPrint :: a -> m () }
    
        data MonadTime m = MonadTime { getTime :: m Integer }
    
        ioMonadTime :: IO Integer
        ioMonadTime = do
            TOD s _ <- getClockTime
            return s
    
        printTime :: Monad m => MonadPrint m Integer -> MonadTime m -> m ()
        printTime MonadPrint{..} MonadTime{..} = getTime >>= doPrint
    
        main :: IO ()
        main = printTime (MonadPrint print) (MonadTime ioMonadTime)
     
        

Good read on the subject of being careful with overusing typeclasses:

[http://www.haskellforall.com/2012/05/scrap-your-type-
classes...](http://www.haskellforall.com/2012/05/scrap-your-type-classes.html)

~~~
platz
Of course typeclasses can be viewed as dependency injection as well. It its
just that with typeclasses the injection is implicit rather than explicit. But
mechanically they are identical

~~~
fractalsea
This is what I thought when I first saw the parent comment. It looks like an
interesting alternative approach, but from the example I am not clear on why
it is more flexible than the version using typeclasses.

I reckon I will need to re-implement my mocks using the technique you describe
to really understand the motivation.

~~~
gamegoblin
I didn't provide an example of the flexibility for sake of brevity.

The key point I want to make with regard to flexibility is the ability to have
_multiple_ "instances" and switch between them based on run-time values.

For a trivial example, consider searching an ordered array. Let me define the
typeclass:

    
    
        class Container a where
            type Item a
            size :: a -> Int
            contains :: Item a -> a -> Bool
    

Now consider that I have the type SortedArray that I want to make an instance
of Container.

    
    
        instance Container (SortedArray a) where
            type Item (SortedArray a) = a
            size = sortedArraySize
            contains = ???
    

What should my `contains` be? I could linear search or I could binary search.
Depending on the size of the array, either could be faster (due to cache
performance and constant overheads). For small arrays, linear search could be
faster, and for large arrays, binary search could be faster.

I could write my method like this:

    
    
        contains x xs = if size xs <= 64
            then linearSearch x xs
            else binarySearch x xs
    

But having that 64 hard coded in is sort of lame. Suppose I am writing
software that will run on unknown hardware. I don't know the cache properties
of it. Perhaps 16 is better. Perhaps 256. Since my application could be super
high performance, I need it to be nearly optimal.

So on application start-up, I call a function which instantiates arrays of
varying sizes and tries linear search vs. binary search to find the point at
which one outperforms the other.

How can I use this value? I can't inject it in place of that 64 above (without
unsafePerformIO...).

The solution is to instead represent the typeclass at the value level:

    
    
        data Container a b = Container
            { size :: a -> Int
            , contains :: b -> a -> Bool
            }
    

Now I can have code that looks like:

    
    
        main = do
            n <- findOptimalSearchSplitPoint
    
            let sortedArraySearch x xs = if sortedArraySize xs <= n
                    then linearSearch x xs
                    else binarySearch x xs
            let container = Container sortedArraySize sortedArraySearch
    

Then I just inject that container "instance" wherever it needs to go.

------
LukeHoersten
Pusher is doing some awesome stuff with Haskell! They run a lot of the bitcoin
exchange web socket APIs on their infrastructure. I'm sure they have other
users too. Cool stuff.

------
bartq
finally someone did haskell examples in decent modern blog UI instead of old
fashioned dark depressing terminal-like colors ;)

~~~
TazeTSchnitzel
Code can be beautiful, shame so many people consider xterm to be the height of
beauty.

