使用monad堆栈进行依赖注入

Dam*_*les 6 haskell dependency-injection separation-of-concerns

我正在尝试不同的方法来做有时被称为依赖注入的方法.为此,我详细阐述了天气应用程序的一个简单示例,我们想要获取天气数据(来自网络服务或硬件设备),存储天气数据(可以是数据库或只是文件),并报告(将其打印到屏幕或说出天气).我们的想法是编写使用一些程序fetch,storereport功能,它的实现可以有所不同.

我已经设法将问题和摘要从使用函数free-monad的检索,存储和报告的实现中分离出来,但是我使用monad堆栈达到的解决方案看起来很糟糕:

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

在上面的代码,都DummyStorageDummyReporter必须有琐碎的情况下进行WeatherService,这似乎是完全错误的.而且,这些实例取决于最终堆叠monad的顺序.有没有办法避免不同堆栈之间的信息泄漏?

dan*_*iaz 3

也许您可以拥有需要访问 IO 和一些必要的簿记状态的“自由浮动”实现函数,而不是将实现绑定到特定的新类型,例如

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

“注入”意味着创建一些新类型来StateT承载所需的状态,然后声明实例,例如

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

_1来自microlenszoommicrolens -mtl。)