быть достаточно. В более конкретных сценариях, возможно, эти ограничения также имеют смысл.
буя разные подходы делать то, что иногда называют инъекцией зависимости. Для этого я разработал простой пример приложения погоды, где мы хотим получить данные о погоде (из веб-службы или с аппаратного устройства), сохранить данные о погоде (это может быть база данных или просто файл), и сообщить об этом (либо распечатать его на экран, или говорить погоду). Идея состоит в том, чтобы написать программу, которая использует некоторые, а такжеfetch
, store
функции, реализация которых может варьироваться.report
Мне удалось отделить проблемы и абстрагироваться от реализации поиска, хранения и отчетности с помощью
функции а такжебесплатно-монадыОднако решение, которое я достиг с помощью стека монад, выглядит плохо:В приведенном выше коде оба
{-# 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
а такжеDummyStorage
должны иметь тривиальные случаи дляDummyReporter
, что, кажется, явно неправильно. Более того, эти случаи зависят от порядка монады, сложенного в конце. Есть ли способ избежать утечки информации между различными стеками?WeatherService
«В приведенном выше коде и DummyStorage, и DummyReporter должны иметь тривиальные экземпляры для WeatherService, что кажется совершенно неверным». Нет, так работает MTL. У всех преобразователей есть экземпляры для всех классов типов (ну, в основном), чтобы вы могли сложить их в любом порядке.