import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Reader (MonadReader, ReaderT, asks) import Control.Monad.Trans.Reader (runReaderT) import Control.Monad ((>=>)) {- ********************* Section 0: Fake Stuff ********************* -} -- Fake types for Environments type Manager = String type FileHandler = String -- Fake for the sake of example type Settings = String type Path = String -- fake Manager init newManager :: Settings -> IO Manager newManager settings = pure $ settings <> ": " <> "NewManager" -- fake File init newFileHandler :: Path -> IO FileHandler newFileHandler pth = pure $ "FileHandler at " <> pth {- ***************************************** Section 1: abstract definition of the API All functions are abstracted over its effects ***************************************** -} class HasManager env where acquireManager :: env -> Manager class HasFileHandler env where acquireFileHandler :: env -> FileHandler askManager :: (MonadReader env m, HasManager env) => m Manager askManager = asks acquireManager askFileHandler :: (MonadReader env m, HasFileHandler env) => m FileHandler askFileHandler = asks acquireFileHandler computeA :: (MonadReader env m, HasManager env, MonadIO m) => Int -> m String computeA session_id = do manager <- askManager liftIO . putStrLn $ manager <> " " <> show session_id pure $ show session_id computeB :: (MonadReader env m, HasFileHandler env, MonadIO m) => String -> m () computeB v = do fhandler <- askFileHandler liftIO . putStrLn $ "logging " <> show v <> " to the " <> fhandler pure () computeAandB :: (MonadReader env m, HasManager env, HasFileHandler env, MonadIO m) => Int -> m () computeAandB = computeA >=> computeB {- ***************************************** Section 2: Concrete implementation We define a concrete thing: - A type called Env with two field - Instances for such type to use abstractions - A Lib type which implements MonadReader Env and MonadIO Because our concrete types implement the right abstractions, then we can use the abstracted functions over them ***************************************** -} data Env = Env {fileHandler :: FileHandler, manager :: Manager} instance HasManager Env where acquireManager = manager instance HasFileHandler Env where acquireFileHandler = fileHandler newtype Lib a = Lib (ReaderT Env IO a) deriving (Functor, Applicative, Monad, MonadIO, MonadReader Env) -- Generically run a Lib computation with and Env run :: Env -> Lib a -> IO a run env (Lib x) = runReaderT x env main = do putStrLn "init application" let settings = "(user: haskell)" file_path = "/usr/local/file.json" mang <- newManager settings fhandler <- newFileHandler file_path let init_env = Env fhandler mang run init_env (computeAandB 42)