Вот небольшая игрушка DSL в набранном без тега окончательном стиле (см. Типизированные окончательные переводчики без тега О. Киселева).
class Monad m => RPCToy m where
mkdir :: FilePath -> m ()
ls :: FilePath -> m [FilePath]
Различные установкиэтот маленький DSL будет, например, реализацией mkdir
и ls
на разных платформах, как локальных, так и удаленных. Тип m
является монадой во всех реализациях, это может быть IO
, или предоставленная некоторой сетевой библиотекой, или какой-то другой доморощенной монадой.
Вот реализация в IO
:
import System.Directory (listDirectory)
import Control.Monad (void)
instance RPCToy IO where
mkdir = void . putStrLn . ("better not create "++)
ls = listDirectory
и небольшое приложение
import Control.Monad (unless)
demo :: RPCToy m => m ()
demo = do
files <- ls "."
unless ("test" `elem` files) $
mkdir "test"
, которое можно запустить в IO
монаде
main :: IO ()
main = do
demo
Пока все хорошо.
Теперь предположим, что разные реализации полагаются на одну и ту же монаду m
, например, из одной сетевой библиотеки. Для того, чтобы типизированный конечный стиль без тегов работал, здесь необходимы разные монады, которые, тем не менее, по сути одинаковы. Неоднозначность можно устранить, обернув вещи:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Local a = Local {runLocal :: IO a} deriving (Functor, Applicative, Monad)
, а затем внедрив RPCToy Local
,
instance RPCToy Local where
mkdir = Local . putStrLn . ("BETTER NOT CREATE "++)
ls = Local . listDirectory
, которые можно прекрасно запустить
main :: IO ()
main = do
runLocal demo
Что меня беспокоит, так это: Разработчики должны поместить в код много Local
с, или, довольно многократно, обернуть библиотечные функции следующим образом:
localListDirectory = Local . listDirectory
...
Одна идея состоит в том, чтобысоздайте 'индексированную монаду' im i a
, im i
являющуюся монадой, которая содержит индексный тип i
с единственной целью позволить компилятору различать различные реализации. Расширение RebindableSyntax
делает это возможным без отказа от синтаксиса do
. Но каждую монаду нужно «поднять» в эту индексированную монаду. Улучшение заключается в следующем: каждая монада m
и ее функции должны быть сняты только один раз. Иначе это все еще довольно запутанно.
Мне интересно, есть ли лучший способ избавиться от обертывания монады.