Last active
April 24, 2019 14:22
-
-
Save friedbrice/527766140f76674d6be6d957109a8d39 to your computer and use it in GitHub Desktop.
How (and how not) to make testable code in Haskell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# 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