Ответ 1
Как упоминалось в комментариях, часто желательно иметь некоторую абстракцию между реализацией кода и базы данных. Вы можете получить большую часть той же абстракции, что и свободная монада, определив класс для вашего DB Monad (я взял пару привилегий здесь):
class (Monad m) => MonadImageDB m where
indexImage :: (ImageId, UTCTime) -> Exif -> Thumbnail -> m SaveResult
removeImage :: ImageId -> m DeleteResult
Если ваш код написан против MonadImageDB m =>
вместо плотно связанного с DBM
, вы сможете поменять базу данных и обработку ошибок, не изменяя свой код.
Почему вы вместо этого используете бесплатный? Поскольку "освобождает интерпретатора как можно больше" , это означает, что intrereter предназначен только для обеспечения монады, и ничего больше. Это означает, что вы так же безукоризненны, как записываете экземпляры монады, чтобы пойти с вашим кодом. Обратите внимание, что для свободной монады вы не пишете свой собственный экземпляр для Monad
, вы получите его бесплатно. Вы пишете что-то вроде
data DBActionF next =
SaveDocument RawDocument ( next)
| GetDocuments DocumentFilter ([RawDocument] -> next)
| GetDocumentStats ([(DocId, DocumentStats)] -> next)
вывести Functor DBActionF
и получить экземпляр monad для Free DBActionF
из существующего экземпляра для Functor f => Monad (Free f)
.
Для вашего примера это будет:
data ImageActionF next =
IndexImage (ImageId, UTCTime) Exif Thumbnail (SaveResult -> next)
| RemoveImage ImageId (DeleteResult -> next)
Вы также можете получить свойство "освобождает интерпретатор как можно больше" для класса типа. Если у вас нет других ограничений на m
, чем класс типа, MonadImageDB
, и все методы MonadImageDB
могут быть конструкторами для Functor
, тогда вы получите одно и то же свойство. Вы можете увидеть это, выполнив instance MonadImageDB (Free ImageActionF)
.
Если вы собираетесь смешивать свой код с взаимодействиями с какой-либо другой монадой, вы можете получить монадный трансформатор из бесплатного вместо монады.
Выбор
Вам не нужно выбирать. Вы можете конвертировать назад и вперед между представлениями. В этом примере показано, как это сделать для действий с нулевым, одним или двумя аргументами, возвращающими нулевой, один или два результата. Во-первых, немного шаблона
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
import Control.Monad.Free
У нас есть класс типа
class Monad m => MonadAddDel m where
add :: String -> m Int
del :: Int -> m ()
set :: Int -> String -> m ()
add2 :: String -> String -> m (Int, Int)
nop :: m ()
и эквивалентное функторное представление
data AddDelF next
= Add String ( Int -> next)
| Del Int ( next)
| Set Int String ( next)
| Add2 String String (Int -> Int -> next)
| Nop ( next)
deriving (Functor)
Преобразование из свободного представления в класс типа заменяет Pure
на return
, Free
на >>=
, Add
на Add
и т.д.
run :: MonadAddDel m => Free AddDelF a -> m a
run (Pure a) = return a
run (Free (Add x next)) = add x >>= run . next
run (Free (Del id next)) = del id >> run next
run (Free (Set id x next)) = set id x >> run next
run (Free (Add2 x y next)) = add2 x y >>= \ids -> run (next (fst ids) (snd ids))
run (Free (Nop next)) = nop >> run next
A MonadAddDel
экземпляр для представления создает функции для аргументов next
конструкторов с использованием Pure
.
instance MonadAddDel (Free AddDelF) where
add x = Free . (Add x ) $ Pure
del id = Free . (Del id ) $ Pure ()
set id x = Free . (Set id x) $ Pure ()
add2 x y = Free . (Add2 x y) $ \id1 id2 -> Pure (id1, id2)
nop = Free . Nop $ Pure ()
(У обоих из них есть шаблоны, которые мы могли бы извлечь для производственного кода, сложная часть для их записи в общем случае имела бы дело с изменяющимся числом аргументов ввода и результата)
Кодирование по типу класса использует только ограничение MonadAddDel m =>
, например:
example1 :: MonadAddDel m => m ()
example1 = do
id <- add "Hi"
del id
nop
(id3, id4) <- add2 "Hello" "World"
set id4 "Again"
Мне было слишком лениво написать другой экземпляр для MonadAddDel
, кроме того, что я получил бесплатно, и слишком ленив, чтобы сделать пример, кроме того, используя класс MonadAddDel
.
Если вам нравится запускать пример кода, достаточно увидеть пример, который интерпретируется один раз (преобразование представления класса типа в свободное представление) и снова после преобразования бесплатного представления обратно в представление класса типа снова. Опять же, я слишком ленив, чтобы написать код дважды.
debugInterpreter :: Free AddDelF a -> IO a
debugInterpreter = go 0
where
go n (Pure a) = return a
go n (Free (Add x next)) =
do
print $ "Adding " ++ x ++ " with id " ++ show n
go (n+1) (next n)
go n (Free (Del id next)) =
do
print $ "Deleting " ++ show id
go n next
go n (Free (Set id x next)) =
do
print $ "Setting " ++ show id ++ " to " ++ show x
go n next
go n (Free (Add2 x y next)) =
do
print $ "Adding " ++ x ++ " with id " ++ show n ++ " and " ++ y ++ " with id " ++ show (n+1)
go (n+2) (next n (n+1))
go n (Free (Nop next)) =
do
print "Nop"
go n next
main =
do
debugInterpreter example1
debugInterpreter . run $ example1