быть достаточно. В более конкретных сценариях, возможно, эти ограничения также имеют смысл.

буя разные подходы делать то, что иногда называют инъекцией зависимости. Для этого я разработал простой пример приложения погоды, где мы хотим получить данные о погоде (из веб-службы или с аппаратного устройства), сохранить данные о погоде (это может быть база данных или просто файл), и сообщить об этом (либо распечатать его на экран, или говорить погоду). Идея состоит в том, чтобы написать программу, которая использует некоторые, а также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 @[email protected]
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 @[email protected]
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
-- (@[email protected]) ...

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

-- | A dummy implementation of the @[email protected]
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. У всех преобразователей есть экземпляры для всех классов типов (ну, в основном), чтобы вы могли сложить их в любом порядке.

 Damian Nadales30 нояб. 2017 г., 18:43
 магия?).GeneralizedNewtypeDerivingВы используете трансформаторы, вы просто не написали экземпляры
 Benjamin Hodgson♦30 нояб. 2017 г., 18:50
, например,MonadTrans, Боюсь, я не понимаю ваш вопрос о сквозных проблемахinstance MonadTrans DummyReporter where lift = DummyReporterЯ писал
 Benjamin Hodgson♦30 нояб. 2017 г., 18:40
пример из исходного кода MTL.Хорошо, тогда это кажется неизбежным. И теперь, когда вы упомянули об этом, в приведенном выше коде я вообще не использую преобразователи (возможно, благодаря
 Mark Seemann30 нояб. 2017 г., 19:57
довольно широко по этой теме, В этом случае, я бы вопрос, если вы даженужно ДИ или альтернатива. По сути, все, что вы описываете, является нечистым, с небольшой (если таковая имеется) логикой, поэтому вы должны иметь возможность просто составлять (нечистые) функции вместе: 1. Получать данные о погоде (нечистые). 2. переведите это (возможно, чисто). 3. Отчет (побочные эффекты, нечистый). Просто объедините функции в цепочку. Зачем вам MTL или трансформаторы? Какую ценность это обеспечивает?Кажется, вы уже нашли хотя бы одно правильное решение этой проблемы (связанный модуль,
 user240703830 нояб. 2017 г., 20:38
). Это хорошо известное ограничение монадных трансформаторов, которые вам нужныWeatherReporterFreeSeparated случаи (гдеn*m= количество трансформаторов иn= количество классов) - например,m имеет «тривиальные» экземпляры дляReaderTи т. д. Однако порядок не имеет значения (вы можете иметь экземпляры для всех 3 классов для всех 3 типов). Если нет веских причин использовать монадные трансформаторы, я бы порекомендовал вам использовать решение, которое вы уже нашли.MonadWriter, MonadStateВместо привязки реализаций к конкретным новым типам, возможно, вы могли бы иметь «свободно плавающие» функции реализации, которые требовали доступа к IO и некоторому необходимому состоянию учета, например

Ответы на вопрос(1)

Решение Вопроса

«Инъекция» будет означать создание какого-то нового типа над

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

 несут необходимые состояния, а затем объявляют такие случаи, как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

(_1микролинз а также изzoomмикролинз-МТЛ.)Выглядит неплохо. Я думаю, что я достиг

 danidiaz06 дек. 2017 г., 12:01
 и ограничимся использованиемStateT надReaderT и изменяемые ссылки для переноса состояния, эти шаблоны хорошо сочетаются с подходом "unliftio"IOhackage.haskell.org/package/unliftioyoutube.com/watch?v=KZIN9f9rI34 Спасибо за ссылки. Я не знал о unliftio. Для примера, который я привел, я бы ожидал
 Damian Nadales04 дек. 2017 г., 16:27
аналогичное решение используя монадические функции.@DamianNadales Я расширил свой ответ в этой сути
 danidiaz04 дек. 2017 г., 19:27
gist.github.com/danidiaz/5fb8ac9ecf40875617cba503214dd3c2 Моя цель состояла в том, чтобы избежать стандартного масштабирования каждого метода класса типов при объявлении экземпляров для Новый тип. Интересно, что я тоже использовалInjected, но, возможно, немного переборщил с типом махинаций.MultiParamTypeClasses@DamianNadales Если мы избегаем
 Damian Nadales06 дек. 2017 г., 13:03
 а такжеReaderT быть достаточно. В более конкретных сценариях, возможно, эти ограничения также имеют смысл.IOВнедрение зависимостей с использованием стека

Ваш ответ на вопрос