使用monad堆栈进行依赖注入

编程入门 行业动态 更新时间:2024-10-11 17:26:21
本文介绍了使用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堆栈进行依赖注入

本文发布于:2023-11-29 05:52:58,感谢您对本站的认可!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:堆栈   monad

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!