使用monad堆栈进行依赖注入 [英] Doing dependency injection using monad stacks
问题描述
我是尝试不同的方法来执行有时称为依赖注入的操作。为此,我详细阐述了一个简单的天气应用示例,我们要从中获取天气数据(从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屋!