在Haskell中不使用Transformer monad而将更改应用于外部Monad

but5z9lq  于 9个月前  发布在  其他
关注(0)|答案(1)|浏览(60)

我正在尝试做类似于下面的事情,我想在State-Monad的状态不满足特定条件的情况下返回错误消息,可以在不使用liftExceptT或其他Transformer monad的情况下完成吗?

type Log = State [String] ()

stateCheckDuplicate :: String -> Either String Log
stateCheckDuplicate elem = 
  Right (do 
    log <- get
    if lookup elem log /= Nothing then Left "Duplicate elements"
    else    
      (put (elem:log)))
7dl7o3gd

7dl7o3gd1#

如果我理解stateCheckDuplicate应该做什么,你需要stateCheckDuplicate同构于String -> [String] -> Either String [String]的类型。然后你的实现看起来像这样:

stateCheckDuplicate :: String -> [String] -> Either String [String]
stateCheckDuplicate msg logs
    | msg `elem` logs = Left "Duplicate elements"
    | otherwise       = Right $ msg : logs

正如你所看到的,没有State,也没有ExceptT,也没有liftXXX。但是可能有一个问题,这个函数很难与其他函数组合。要解决这个问题需要了解你试图做什么。
最新情况:
好的,我想在一个do块中使用它来执行一系列操作,这样状态(或者在本例中是字符串)将在每次操作时更新。然后,这些操作中的每一个又可以使用例如mapM_等来处理。如果State或String-function返回Left部分,我希望带有do块的函数返回Left“Duplicate elements”,否则带有do块的函数将返回完全不同的类型,可能是String(Int,Int)或其他类型。
为了在do-block中使用这个操作,结果类型应该是类Monad的示例。您可以为此目的创建自己的类型。

newtype LogM a = LogM { runLogM :: [String] -> (Either String a, [String]) }

instance Functor LogM where
    fmap f m = LogM $ \log0 ->
        let (ex, log1) = runLogM m log0 in
        case ex of
            Right x  -> (Right $ f x, log1)
            Left err -> (Left err, log1)

instance Applicative LogM where
    pure x = LogM $ \log -> (Right x, log)
    mf <*> mx = do
        f <- mf
        x <- mx
        pure $ f x

instance Monad LogM where
    m >>= k = LogM $ \log0 ->
        let (ex, log1) = runLogM m log0 in
        case ex of
            Right x  -> runLogM (k x) log1
            Left err -> (Left err, log1)

instance MonadState [String] LogM where
    state f = LogM $ \log0 ->
        let (x, log1) = f log0 in
        (Right x, log1)

instance MonadError String LogM where
    throwError err = LogM $ \log -> (Left err, log)
    catchError m h = LogM $ \log0 ->
        let (ex, log1) = runLogM m log0 in
        case ex of
            Right x  -> (Right x, log1)
            Left err -> runLogM (h err) log1

stateCheckDuplicate :: String -> LogM ()
stateCheckDuplicate msg = do
    log <- get
    if msg `elem` log then
        throwError "Duplicate elements"
    else
        put $ msg : log

但是,比较一下使用变压器会有多容易:

type LogM = ExceptT String (State [String])

stateCheckDuplicate :: String -> LogM ()
stateCheckDuplicate msg = do
    log <- get
    if msg `elem` log then
        throwError "Duplicate elements"
    else
        put $ msg : log

如果你不想要类型别名,你可以这样做:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

newtype LogM a = LogM { runLogM :: ExceptT String (State [String]) a }
    deriving ( Functor, Applicative, Monad
             , MonadState [String]
             , MonadError String
             )

相关问题