Haskell - 如何将具有不同依赖子集的组件连接在一起?

sev*_*evo 3 haskell dependency-injection

如何从需要各种基础结构功能子集的组件中集成应用程序?

有些将非常简单,只需要一个配置阅读器(我想只为每个业务功能公开一个相关的子集),也许还需要一个记录器.有些还需要连接到外部服务(使用缓存),我还希望与外部世界进行有限范围的可能交互.

我不想处理明确地将多个参数传递给这些函数,或者将它们包装成MonadIO能够执行所有操作的函数.

在Java应用程序容器中注入多个依赖项最接近的是什么?

Cir*_*dec 5

MTL库具有类型类来表示,其从环境读取配置,计算MonadReader,并将数据写入像一个记录器,MonadWriter.我们将这些用于我们的示例.

MonadReader我们将使用的部分是

class Monad m => MonadReader r m | m -> r where
    ask :: m r
Run Code Online (Sandbox Code Playgroud)

MonadWriter我们将使用的部分是

class (Monoid w, Monad m) => MonadWriter w m | m -> w where Source  
    tell :: w -> m ()
Run Code Online (Sandbox Code Playgroud)

要求" 多个依赖项",我们需要一个m为多个类型类提供实例的单个.

样板

最终,我们将使用ReaderTWriterTIdentity变压器的运行我们的榜样.

{-# 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
Run Code Online (Sandbox Code Playgroud)

使用多个依赖项:读取配置和日志记录

我们的读者将阅读以下环境; 它会提供一个MonadReader Configuration.

data Configuration = Config { site :: String }
    deriving Show
Run Code Online (Sandbox Code Playgroud)

我们的记录器将累积一个消息列表,每个消息都是一个字符串.它将提供一个MonadWriter [String].

您可以通过要求多个类型类的实例来要求多种功能.为此,您可以要求一个m具有两个实例的单个MonadReaderMonadWriter.这是一个需要环境来读取配置的组件以及编写日志消息的方法.

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

为变换器提供多个依赖项

我们可以提供必要的m而不需要接触IO.ReaderT来自变形金刚增加了从环境中读取的能力Monad; 我们将用它来提供MonadReader Configuration.WriterT来自变形金刚增加了积累输出的能力Monad; 我们将用它来提供MonadWriter [String].对于潜在的Monad,我们只是Identity用来炫耀我们没有搞乱IO.以下提供了MonadReader ConfigurationMonadWriter [String],并且在不使用的情况下运行计算IO.

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

runDepsIdentity :: DepsIdentity a -> Configuration -> (a, [String])
runDepsIdentity ma = runIdentity . runWriterT . runReaderT ma
Run Code Online (Sandbox Code Playgroud)

举个例子

我们将logConfig在另一个更大的例子中使用我们之前的版本:

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

最后,我们将运行example一个Configuration,看看它做什么.请注意,IO此处仅用于输出最终结果以运行示例.

main :: IO ()
main = print . runDepsIdentity example $ Config {site = "StackOverflow"}
Run Code Online (Sandbox Code Playgroud)

这会产生以下输出

((),["Starting","Logging Config","Config {site = \"StackOverflow\"}","Done Logging Config","Done"])
Run Code Online (Sandbox Code Playgroud)