Haskell - how to combine components that have different subsets of dependencies?

How can I integrate an application of components that require different subsets of the infrastructure?

Some of them will be very simple and require only a configuration reader (I only want to expose the appropriate subset for each business function) and possibly a logger. Some will also need to connect to external services (with a cache) where I also want to expose the limited amount of possible interactions with the outside world.

I don't want to deal with passing multiple arguments to such functions explicitly or wrapping them in some MonadIO

that can do everything.

What would be the closest thing to multiple injection like in Java application containers?

+3


source to share


1 answer


The library mtl there are classes of classes to represent computations that read the configuration of the environment, MonadReader

and record the data in something like a registrar MonadWriter

. We will use them for our examples.

The part MonadReader

we will be using will be

class Monad m => MonadReader r m | m -> r where
    ask :: m r

      

The part MonadWriter

we will be using will be

class (Monoid w, Monad m) => MonadWriter w m | m -> w where Source  
    tell :: w -> m ()

      

To require " multiple dependencies ", we need one m

that provides instances for multiple typeclasses.

Boilerplate

We will end up using ReaderT

both and WriterT

and Identity

from transformers to run our example.

{-# LANGUAGE FlexibleContexts #-}

--mtl
import Control.Monad.Reader.Class
import Control.Monad.Writer.Class

--transformers
import Control.Monad.Trans.Reader hiding (ask)
import Control.Monad.Trans.Writer.Strict hiding (tell)
import Data.Functor.Identity

      

Using multiple dependencies: reading configuration and logging

Our reader will read next Wednesday; he will provide MonadReader Configuration

.

data Configuration = Config { site :: String }
    deriving Show

      

Our recorder will accumulate a list of messages, each of which is a string. He will provide MonadWriter [String]

.



You can require multiple capabilities by requiring instances of multiple typeclasses. To do this, you may need one m

that has instances for MonadReader

and MonadWriter

. Here is the component that requires an environment to read the configuration and a way to write log messages.

logConfig :: (MonadWriter String m, MonadReader Configuration m) => m ()
logConfig = do 
    config <- ask
    tell [show config]

      

Providing Multiple Dependencies with Transformers

We can provide the required m

without touching IO

. ReaderT

from transformers adds the ability to read from medium to Monad

; we will use this to provide MonadReader Configuration

. WriterT

from transformers adds that the ability to store output in Monad

; we will use this to provide MonadWriter [String]

. For the basic one Monad

we just use Identity

to show that we are not messing with IO

. Below are both MonadReader Configuration

, and MonadWriter [String]

, and the computation is performed without using IO

.

type DepsIdentity =  ReaderT Configuration (WriterT [String] Identity)

runDepsIdentity :: DepsIdentity a -> Configuration -> (a, [String])
runDepsIdentity ma = runIdentity . runWriterT . runReaderT ma

      

Running the example

We'll use our previous one logConfig

in another, larger example:

example :: (MonadWriter [String] m, MonadReader Configuration m) => m ()
example = do
    tell ["Starting", "Logging Config"]
    logConfig
    tell ["Done Logging Config", "Done"]

      

Finally, we'll run example

with help Configuration

and see what it does. Note that IO

this is only used to output the final output to run the example.

main :: IO ()
main = print . runDepsIdentity example $ Config {site = "StackOverflow"}

      

This creates the following output

((),["Starting","Logging Config","Config {site = \"StackOverflow\"}","Done Logging Config","Done"])

      

+5


source







All Articles