Решение этой проблемы заключается в изменении чистых вспомогательных функций. Мы не хотим, чтобы они были чистыми, мы хотим пропустить один побочный эффект - читают ли они определенные части данных.
Допустим, мы имеем чистую функцию, которая использует только одежду и монеты:
moreVanityThanWealth :: IntMap Clothing -> IntMap Coins -> Bool
moreVanityThanWealth clothing coins = ...
Это, как правило, приятно знать, что функция только заботится о, например, одежду и монеты, но в вашем случае это знание не имеет значения и просто создает головные боли. Мы намеренно забудем эту деталь. Если бы мы следовали за предложением mb14, мы бы передали целые чистые MudData'
, как и вспомогательные функции.
data MudData' = MudData' { _armorTbl :: IntMap Armor
, _clothingTbl :: IntMap Clothing
, _coinsTbl :: IntMap Coins
moreVanityThanWealth :: MudData' -> Bool
moreVanityThanWealth md =
let clothing = _clothingTbl md
coins = _coinsTbl md
in ...
MudData
и MudData'
практически идентичны друг другу. Один из них обертывает свои поля в TVar
, а другой - нет. Мы можем изменить MudData
так, чтобы потребовался дополнительный параметр типа (вид * -> *
) для того, для чего нужно поместить поля. MudData
будет иметь слегка необычный вид (* -> *) -> *
, который тесно связан с объективами, но не имеет большой поддержки библиотеки. Я называю этот шаблон a Модель.
data MudData f = MudData { _armorTbl :: f (IntMap Armor)
, _clothingTbl :: f (IntMap Clothing)
, _coinsTbl :: f (IntMap Coins)
Мы можем восстановить оригинальный MudData
с MudData TVar
. Мы можем воссоздать чистую версию, обернув поля в Identity
, newtype Identity a = Identity {runIdentity :: a}
.С точки зрения MudData Identity
, наша функция будет записана как
moreVanityThanWealth :: MudData Identity -> Bool
moreVanityThanWealth md =
let clothing = runIdentity . _clothingTbl $ md
coins = runIdentity . _coinsTbl $ md
in ...
Мы успешно забыли, какие части MudData
мы использовали, но теперь мы не имеем детализацию замка мы хотим. Нам нужно восстановить, как побочный эффект, именно то, что мы только что забыли. Если мы написали версию помощника STM
это будет выглядеть
moreVanityThanWealth :: MudData TVar -> STM Bool
moreVanityThanWealth md =
do
clothing <- readTVar . _clothingTbl $ md
coins <- readTVar . _coinsTbl $ md
return ...
Это STM
версия для MudData TVar
почти точно так же, как чистая версия, мы только что написали для MudData Identity
. Они различаются только по типу ссылки (TVar
против Identity
), какую функцию мы используем для получения значений из ссылок (readTVar
против runIdentity
) и как возвращается результат (в STM
или в виде простого значения). Было бы неплохо, если бы одна и та же функция могла использоваться для обеспечения обоих. Мы собираемся извлечь то, что является общим для двух функций. Для этого мы вводим класс типа MonadReadRef r m
для Monad
s, с которого мы можем прочитать некоторый тип ссылок. r
- тип ссылки, readRef
- это функция для получения значений из ссылок, а m
- это результат возврата результата. Следующие MonadReadRef
тесно связаны с классом MonadRef
от ref-fd.
{-# LANGUAGE FunctionalDependencies #-}
class Monad m => MonadReadRef r m | m -> r where
readRef :: r a -> m a
Пока код параметризируется по всем MonadReadRef r m
с, это чисто. Мы можем это увидеть, выполнив его со следующим примером MonadReadRef
для обычных значений, хранящихся в Identity
. id
в readRef = id
совпадает с return . runIdentity
.
instance MonadReadRef Identity Identity where
readRef = id
Мы перепишем moreVanityThanWealth
в терминах MonadReadRef
.
moreVanityThanWealth :: MonadReadRef r m => MudData r -> m Bool
moreVanityThanWealth md =
do
clothing <- readRef . _clothingTbl $ md
coins <- readRef . _coinsTbl $ md
return ...
Когда мы добавим MonadReadRef
экземпляр для TVar
с в STM
, мы можем использовать эти «чистые» вычисления в STM
но просочиться побочный эффект которого были зачитаны TVar
s.
instance MonadReadRef TVar STM where
readRef = readTVar
Вы передаете много «IntMap' вместо« MudData »? Будет ли последний вариант? Трудно сказать, не видя какого-то кода - можете ли вы поделиться небольшим фрагментом, показывающим случай, когда помощнику нужно так много аргументов? – chi
Да, это определенно запах кода. Нет, я не знаю, как правильно это исправить. – dfeuer
Я не очень хорошо знаком с TVar, но разве вы не можете его разложить? т.е. удалить все TVar из MUDData и использовать (TVar MUDData), где это необходимо? – mb14