我想编写一个Haskell程序,读取配置并执行某些操作。配置是一个Data.Map
,将被注入到Reader中。当我们找不到配置项时,阅读应该被中断。只是停止,不需要错误消息。所以我只想要一个Maybe单子(而不是Either单子)。
问题是,我应该如何堆叠这两个单子,ReaderT Maybe
还是MaybeT Reader
?
我想编写一个Haskell程序,读取配置并执行某些操作。配置是一个Data.Map
,将被注入到Reader中。当我们找不到配置项时,阅读应该被中断。只是停止,不需要错误消息。所以我只想要一个Maybe单子(而不是Either单子)。
问题是,我应该如何堆叠这两个单子,ReaderT Maybe
还是MaybeT Reader
?
我终于想通了,它们两个都可以。
假设每个配置项的值都是一个整数。
ReaderT ConfigMap Maybe Int
的值将会像这样:
ReaderT (\r -> Just 123)
或者
ReaderT (\r -> Nothing)
MaybeT (Reader ConfigMap) Int
的值将类似于:MaybeT (Reader (\r -> Just 123))
或者:
MaybeT (Reader (\r -> Nothing))
使用这两者之一,我们可以先进行一些读取操作,然后根据读取是否返回 Nothing
来决定是否继续。这些值具有不同的外部形状但相同的功能。
以下是我的小演示:
import qualified Data.Map as M
import Control.Monad
import Control.Monad.Reader
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
type Config = M.Map String Int
getConfig :: String -> MaybeT (Reader Config) Int
getConfig key = MaybeT $ do
m <- ask
return $ M.lookup key m
readAll :: Config -> Maybe (Int, Int)
readAll m =
let r = runMaybeT $ do
a <- getConfig "a"
b <- getConfig "b"
return (a, b)
in runReader r m
main :: IO ()
main = do
putStrLn $ show (readAll $ M.fromList [("x", 3), ("b", 4)])
我的第二个演示:
import qualified Data.Map as M
import Control.Monad
import Control.Monad.Reader
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
type Config = M.Map String Int
getConfig :: String -> ReaderT Config Maybe Int
getConfig key = ReaderT $ \r -> M.lookup key r
readAll :: Config -> Maybe (Int, Int)
readAll m =
let r = runReaderT $ do
a <- getConfig "a"
b <- getConfig "b"
return (a, b)
in r m
main :: IO ()
main = do
putStrLn $ show (readAll $ M.fromList [("a", 3), ("b", 4)])
newtype
构造函数的类型,您会发现它们都展开为r -> Maybe a
,这是一个很好的提示,表明它们可能是相同的。但是,在某些情况下,即使具有相同类型,单子运算符的行为也会有所不同,例如ListT(State s)
与StateT s []
。(这是因为ListT
有点损坏...但在这种情况下,这使它成为一个很好的例子!) - luquiReaderT Maybe
和MaybeT Reader
真的是一对特殊的组合。而使这样的一对成为特殊的原因是它们具有相同的“内部形状”(即在消除所有类型构造函数和lambda(\a ->
)后剩余的内容)。顺便说一下,对于可能失败的记录计算,MaybeT Writer
和WriterT Maybe
是不同的:前者将以(Nothing, "日志的前N行")
失败,而后者仅以Nothing
失败。 - kbridge4096