如果出现异常,是否可以访问`WriterT`的部分收集`tell`s?

问题描述 投票:5回答:2

是否有可能让一个WriterT monad能够在异常情况下共享其部分收集的tells?如果我在try之外的runWriterTw似乎被丢弃了。如果我尝试在里面try,我似乎需要MonadUnliftIOMonadUnliftIO听起来像它可以帮助我,但是那个程序包说它只能解除monadic上下文而不是monadic状态,我想是Writer。有没有人用Writer或类似的东西做过这个?

示例伪代码:

x <- runWriterT $ do
  result <- try $ do
    tell "a"
    tell "b"
    error "c"
    tell "d"
  case result of
    Left e -> Just e
    Right a -> Nothing

x `shouldBe` (Just "c", "ab")
haskell
2个回答
3
投票

好吧,你的代码使用error。从道德上讲,所有的赌注都与error有关,因为它表示你的程序中的错误比什么都重要。 IO能够捕捉到它产生的异常这一事实实际上只是一个有趣的怪癖。因此,如果你需要这种行为,最好使用正确的异常monad变换器,比如@ Li-yaoXia推荐的。

-- see Control.Monad.Except
action :: (MonadExcept String m, MonadWriter String m) =>
          m ()
action = do tell "a"
            tell "b"
            throwError "c"
            tell "d"

-- run action and massage it into your format
yourOutput :: (Maybe String, String)
yourOutput = runWriter $ fmap (either Just (const Nothing)) $ runExceptT actions

至于为什么error无法真正起作用(至少,以一种不错的方式),请考虑error _ :: WriterT w m a实际上意味着什么。 error _ :: Int的意思是“这里应该有一个数字,但只是一个错误。” WriterT w m a是一种程序;保存w类型日志的程序类型,做一些其他的东西(m),并返回a。因此,error _ :: WriterT w m a并不意味着“一个抛出可恢复错误的程序,保留w类型的日志”,这意味着“这里应该有一个程序,而只是一个错误。”比喻说,你发布的action伪代码突然耗尽程序,即使类型没有提到你的程序被允许突然终止,你应该(比喻)感谢你的幸运星你可以设置替换程序(使用try)而不是因错误而被正确惩罚!

随着象牙 - towertop的讲道,让我们假设我们真的有

action :: MonadWriter String m => m ()
action = do tell "a"
            tell "b"
            error "c"
            tell "d"

我们只需处理它。假设你使用了懒惰版的Writer,你会很高兴地注意到这一点

runWriter action =
  ( ()
  , "a" ++ "b" ++ (case error "c" of (_, c) -> c) ++ "d"
  )

存在这个函数,通过捕获不纯的异常(不道德的,“实际上没有程序”,我说error是这样的)来“挽救”列表,如果它在评估脊柱时发生的话。

-- can be recast as Free (a,) () -> IO (Free (a,) (Maybe e))
-- essentially, that type encodes the intuition that a list may end in [] (nil)
-- or in an error
salvageList :: Exception e => [a] -> IO ([a], Maybe e)
salvageList xs = catch (do xs' <- evaluate xs
                           case xs' of
                                [] -> return ([], Nothing)
                                (x : tl) -> do (tl', e) <- salvageList tl
                                               return (x : tl', e)
                       ) (\e -> return ([], Just e))

哪个有效:

-- we get the return value, too! that makes me feel... surprisingly weirded out!
yourOutputPlus :: IO ((), Maybe String, String)
yourOutputPlus = do let (val, log) = runWriter action
                    (realLog, error) <- salvageList log
                    return (val, fmap (\(ErrorCall msg) -> msg) error, realLog)

2
投票

如果你希望状态能够在这样的运行时异常中存活,那么最好的办法就是使用可变变量。例如,这是我们在Yesod中使用的方法。 rio库有一个基于可变引用的MonadWriter实例,它以这种方式工作:

#!/usr/bin/env stack
-- stack --resolver lts-13.17 script
{-# LANGUAGE NoImplicitPrelude #-}
import Test.Hspec
import RIO
import RIO.Writer

main = hspec $ it "writer and exceptions" $ do
  ref <- newSomeRef ""
  result <- tryAny $ runRIO ref $ do
    tell "a"
    tell "b"
    error "c"
    tell "d"
  case result of
    Left _ -> pure ()
    Right () -> error "it should have failed!!!"

  written <- readSomeRef ref
  written `shouldBe` "ab"

我在谈话中谈到了这个(及相关要点)“你不想知道关于monad变压器状态的一切”:

© www.soinside.com 2019 - 2024. All rights reserved.