Неявное снятие при объединении / смешивании ограничений класса типов mtl - PullRequest
0 голосов
/ 08 февраля 2019

В настоящее время я выполняю рефакторинг некоторого моего кода на Haskell, который взаимодействует с Data.Time.В конечном итоге у меня есть набор функций, которые взаимодействуют со временем:

getCurrentTime :: IO UTCTime
getCurrentTime = T.getCurrentTime

getCurrentDay :: IO Day
getCurrentDay = T.utctDay <$> getCurrentTime

daysUntil :: Day -> IO Integer
daysUntil day = T.diffDays day <$> getCurrentDay

и т. Д. И т. Д. И т. Д., В конечном счете, это всего лишь мои собственные вспомогательные функции, основанные на T.getCurrentTime из Data.Time.Что является «эффектом» всех этих функций.

Первый рефакторинг, который я сделал с этим кодом, состоял в том, чтобы просто изменить их на использование MonadIO, чтобы позволить им использоваться в различных стеках трансформаторов, совместимых с этимкласс типов:

getCurrentTime :: MonadIO m => m UTCTime
getCurrentTime = liftIO T.getCurrentTime

getCurrentDay :: MonadIO m => m Day
getCurrentDay = T.utctDay <$> getCurrentTime

daysUntil :: MonadIO m => Day -> m Integer
daysUntil day = T.diffDays day <$> getCurrentDay

Это достаточно просто, так как мне просто нужно поднять T.getCurrentTime, а остальные реализации просто следуют его примеру.

Недавно, хотя я читал о заглушке и подделкеэффекты в Haskell, и хотел бы иметь возможность запускать эти функции с поддельным UTCTime результатом для getCurrentTime.

Отбирая некоторые вещи, которые я прочитал в Интернете, и глядя на то, как Pandoc реализует разделениеИз чистых и эффективных операций я придумал следующее:

newtype TimePure a = TimePure
  { unTimePure :: Reader UTCTime a
  } deriving (Functor, Applicative, Monad, MonadReader UTCTime)

newtype TimeEff m a = TimeEff
  { unTimeIO :: m a
  } deriving (Functor, Applicative, Monad, MonadIO)

class (Functor m, Applicative m, Monad m) => TimeMonad m where
  getCurrentTime :: m UTCTime

instance TimeMonad TimePure where
  getCurrentTime = ask

instance MonadIO m => TimeMonad (TimeEff m) where
  getCurrentTime = liftIO T.getCurrentTime

getCurrentDay :: TimeMonad m => m Day
getCurrentDay = T.utctDay <$> getCurrentTime

daysUntil :: TimeMonad m => Day -> m Integer
daysUntil day = T.diffDays day <$> getCurrentDay

Опять же, кроме дополнительных определений в верхней части, мне не пришлось сильно менять - мои оригинальные функции просто нужно изменитьиспользовать TimeMonad m вместо MonadIO m.

Это идеально, и теперь я могу запускать свои функции времени в чистом контексте.

Каккогда-нибудь, когда я приду к какому-то реальному коду, приведу пример такой функции, которая взаимодействует с БД:

markArticleRead :: MonadIO m => Key Article -> SqlPersistT m ()
markArticleRead articleKey =
  updateLastModified articleKey =<< getCurrentTime

Я должен настроить свою функцию следующим образом:

markArticleRead :: (MonadIO m, TimeMonad m) => Key Article -> SqlPersistT m ()
markArticleRead articleKey =
  updateLastModified articleKey =<< lift getCurrentTime

Очевидно, я должен сделать это, поскольку getCurrentTime не нужно MonadIO для запуска.У меня есть проблема с повторным введением лифта, это необходимо, потому что есть два «слоя» трансформаторной батареи, а не один (я думаю, что это подходящее объяснение?).

Один изХорошая новость о введении MonadIO заключалась в том, что он убрал необходимость поднимать вещи повсюду и сделал такие функции, которые в большинстве случаев содержат бизнес-логику и т. д., намного менее шумными.Есть ли для меня способ получить это преимущество, где я могу получить неявное поднятие в стиле mtl, или это сейчас невозможно из-за введенных мною типов?

1 Ответ

0 голосов
/ 08 февраля 2019

Для эффектов в стиле mtl обычно определяют случаи подъема для обычных монадных трансформаторов.Такие как TimeMonad m => TimeMonad (ReaderT r m).Это позволяет вам пропустить lift in markArticleRead.

Другой вариант - пропустить монадный преобразователь TimeEff.Он не несет никакой дополнительной информации, и вы не упоминаете о необходимости предотвращения вызова функций времени в других типах MonadIO.Если вы пишете экземпляр MonadIO m => TimeMonad m, то markArticleRead не нуждается в ограничении TimeMonad или lift.Этот экземпляр перекрывает тот, что в первом абзаце;выберите один.

Если вам нужен монадный преобразователь, вы можете объединить свои TimePure и TimeEff.newtype TimeT m a = TimeT (ReaderT UTCTime m a) позволит вам внедрить выбранные UTCTime в стеки эффекта, которые не включают IO (или ограничения которых не обеспечивают IO).Затем вы можете определить TimePure в терминах TimeT, так как transformers определяет Reader, а остальные.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...