Skip to content

Instantly share code, notes, and snippets.

@friedbrice
Last active April 24, 2019 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save friedbrice/527766140f76674d6be6d957109a8d39 to your computer and use it in GitHub Desktop.
Save friedbrice/527766140f76674d6be6d957109a8d39 to your computer and use it in GitHub Desktop.
How (and how not) to make testable code in Haskell
module TheRightWay where
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe
myProgram :: Monad m => m String -> (String -> m Int) -> Int -> m String
myProgram readLine parseInt n = do
line <- readLine
n' <- parseInt line
return . show $ n + n'
read' :: (MonadPlus m, Read a) => String -> m a
read' str = case reads str of
[(x,"")] -> pure x;
_ -> empty
safe :: Int -> MaybeT IO String
safe = myProgram (liftIO getLine) read'
yolo :: Int -> IO String
yolo = myProgram getLine (pure . read)
test :: Int -> Maybe String
test = myProgram (pure "mock string") read'
{-# LANGUAGE AllowAmbiguousTypes,
DataKinds,
FlexibleContexts,
FlexibleInstances,
FunctionalDependencies,
GeneralizedNewtypeDeriving,
KindSignatures,
MultiParamTypeClasses,
ScopedTypeVariables,
TypeApplications #-}
module TheWrongWay where
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe
import GHC.TypeLits
class Eff (label :: Symbol) (m :: * -> *) (a :: *) (b :: *) where
eff :: a -> m b
myProgram :: forall m.
(Monad m, Eff "readLine" m () String, Eff "parseInt" m String Int) =>
Int -> m String
myProgram n = do
line <- eff @"readLine" @_ @_ @String ()
n' <- eff @"parseInt" line
return . show $ n + n'
instance Eff "readLine" (MaybeT IO) () String where
eff _ = liftIO getLine
instance Eff "parseInt" (MaybeT IO) String Int where
eff str = case reads str of
[(x,"")] -> pure x
_ -> empty
safe :: Int -> MaybeT IO String
safe = myProgram @(MaybeT IO)
instance Eff "readLine" IO () String where
eff _ = getLine
instance Eff "parseInt" IO String Int where
eff str = pure (read str)
yolo :: Int -> IO String
yolo = myProgram @IO
newtype MockIO a = MockIO (Maybe a)
deriving (Functor, Applicative, Monad, Alternative, MonadPlus)
instance Eff "readLine" MockIO () String where
eff _ = pure "mock string"
instance Eff "parseInt" MockIO String Int where
eff str = case reads str of
[(x,"")] -> pure x
_ -> empty
test :: Int -> MockIO String
test = myProgram @MockIO
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment