使用monad堆栈进行依赖注入 [英] Doing dependency injection using monad stacks

查看:149
本文介绍了使用monad堆栈进行依赖注入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是尝试不同的方法来执行有时称为依赖注入的操作。为此,我详细阐述了一个简单的天气应用示例,我们要从中获取天气数据(从Web服务或硬件设备),存储天气数据(可能是数据库或简单文件),并将其报告(打印到屏幕或说出天气)。这个想法是编写一个使用 fetch store report 函数,它们的实现可能会有所不同。



我已经成功地分离了关注点,并使用 functions a>和 free-monads ,然而我用monad stacks达到的解决方案看起来很糟糕:

  { - #LANGUAGE GeneralizedNewtypeDeriving # - } 

模块WeatherReporterMTL其中

导入Control.Monad.IO.Class
导入Control.Monad.Trans.Class

类型WeatherData = String

class Monad m => WeatherService m其中
fetch :: m WeatherData

class Monad m =>存储m其中
store :: WeatherData - > m()

class Monad m => Reporter m其中
report :: WeatherData - > m()

- | @ WeatherService @
的一个虚拟实现$ new $ DummyService ma = DummyService {runDummyService :: ma}
派生(Functor,Applicative,Monad,MonadIO)

实例MonadIO m => ; WeatherService(DummyService m)其中
fetch = return在12月份不会变暖。

- | @ Storage @
的一个虚拟实现newtype DummyStorage ma = DummyStorage {runDummyStorage :: ma}
派生(Functor,Applicative,Monad,MonadIO,WeatherService)

- It似乎是错误的存储必须是天气服务的实例
- (@ WeatherService @)...

实例MonadIO m =>存储(DummyStorage m)其中
存储d = liftIO $ putStrLn $此报告没有剩余空间:++ d

- | @ Reporter @
的一个虚拟实现$ new







$ - 好吧,现在看起来更糟了:我们正在提供关于
的信息 - 我们如何堆叠我们的monads:/

实例MonadIO m => Reporter(DummyReporter m)其中
报告d = liftIO $ putStrLn $这里在MTL端++ d

reportWeather ::(WeatherService m,Storage m,Reporter m)=> ; m()
reportWeather = do
w< - fetch
store w
report w

dummyWeatherReport :: IO()
dummyWeatherReport = runDummyService $ runDummyStorage $ runDummyReporter reportWeather

在上面的代码中, DummyStorage DummyReporter 必须有 WeatherService 的简单实例,这看起来很明显是错误的。而且,这些实例取决于monad堆栈的顺序。有没有办法避免在不同的堆栈之间泄漏信息?解析方案

没有将实现绑定到特定的新类型,也许你可以有自由浮动实现功能,需要访问IO和一些必要的簿记状态,如

 数据WeatherState = WeatherState  - dummy 
fetch'::(MonadState WeatherState m,MonadIO m)=> m WeatherData
fetch'= undefined
data StorageState = StorageState - dummy
store'::(MonadState StorageState m,MonadIO m)=> WeatherData - > m()
store'= undefined
data ReporterState = ReporterState - dummy
report'::(MonadState ReporterState m,MonadIO m)=> WeatherData - > m()
report'= undefined

注入意味着创建一些新类型 StateT 携带所需的状态,然后声明实例像

  newtype Injected a = 
注入{getInjected :: StateT(WeatherState,StorageState,ReportState)a}
派生(Functor,Applicative,Monad)

实例WeatherService注入其中
fetch =注入$ zoom _1 fetch'

实例存储注入其中
存储x =注入$ zoom _2 $存储'x

实例Reporter注入其中
report x = Injected $ zoom _3 $ report'x

_1 来自 microlens zoom from microlens-mtl 。)


I'm trying different approaches to do what is sometimes known as dependency injection. For this I've elaborated a simple example of a weather app, where we want to fetch the weather data (from a web-service or from a hardware device), store the weather data (could be a database or simply a file), and report it (either print it to screen, or speak the weather). The idea is to write a program that uses some fetch, store, and report functions, whose implementations can vary.

I've managed to separate concerns and abstract away from the implementations of retrieval, storage, and reporting using functions and free-monads, however the solution I reached with monad stacks looks bad:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module WeatherReporterMTL where

import           Control.Monad.IO.Class
import           Control.Monad.Trans.Class

type WeatherData = String

class Monad m => WeatherService m where
    fetch :: m WeatherData

class Monad m => Storage m where
    store :: WeatherData -> m ()

class Monad m => Reporter m where
    report :: WeatherData -> m ()

-- | A dummy implementation of the @WeatherService@
newtype DummyService m a = DummyService { runDummyService :: m a }
    deriving (Functor, Applicative, Monad, MonadIO)

instance MonadIO m => WeatherService (DummyService m) where
    fetch = return "won't get any warmer in December."

-- | A dummy implementation of the @Storage@
newtype DummyStorage m a = DummyStorage { runDummyStorage :: m a }
    deriving (Functor, Applicative, Monad, MonadIO, WeatherService)

-- It seems wrong that the storage has to be an instance the weather service
-- (@WeatherService@) ...

instance MonadIO m => Storage (DummyStorage m) where
    store d = liftIO $ putStrLn $ "No room left for this report: " ++ d

-- | A dummy implementation of the @Reporter@
newtype DummyReporter m a = DummyReporter { runDummyReporter :: m a }
    deriving (Functor, Applicative, Monad, MonadIO, WeatherService, Storage)

-- Ok, now this seems even worse: we're putting information about
-- how we're gonna stack our monads :/

instance MonadIO m => Reporter (DummyReporter m) where
    report d = liftIO $ putStrLn $ "Here at the MTL side " ++ d

reportWeather :: (WeatherService m, Storage m, Reporter m) => m ()
reportWeather = do
    w <- fetch
    store w
    report w

dummyWeatherReport :: IO ()
dummyWeatherReport = runDummyService $ runDummyStorage $ runDummyReporter reportWeather

In the code above, both DummyStorage and DummyReporter have to have trivial instances for WeatherService, which seems plainly wrong. Moreover, these instances depend on the order monads are stacked in the end. Is there a way to avoid leaking information between the different stacks?

解决方案

Instead of tying implementations to specific newtypes, perhaps you could have "free-floating" implementation functions that required access to IO and to some necessary bookkeeping state, like

data WeatherState = WeatherState -- dummy
fetch' :: (MonadState WeatherState m,MonadIO m) => m WeatherData
fetch' = undefined 
data StorageState = StorageState -- dummy
store' :: (MonadState StorageState m,MonadIO m) => WeatherData -> m ()
store' = undefined 
data ReporterState = ReporterState -- dummy
report' :: (MonadState ReporterState m,MonadIO m) => WeatherData -> m ()
report' = undefined

"Injecting" would mean creating some newtype over a StateT carrying the required states, and then declaring instances like

newtype Injected a = 
    Injected { getInjected :: StateT (WeatherState,StorageState,ReportState) a } 
    deriving (Functor,Applicative,Monad)

instance WeatherService Injected where
    fetch = Injected $ zoom _1 fetch'

instance Storage Injected where
    store x = Injected $ zoom _2 $ store' x

instance Reporter Injected where
    report x = Injected $ zoom _3 $ report' x

(_1 is from microlens and zoom from microlens-mtl.)

这篇关于使用monad堆栈进行依赖注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆