如果发生异常,是否可以访问“ WriterT”的部分收集的“ tell”?

时间:2019-04-21 20:43:03

标签: haskell

是否有一个WriterT monad在异常情况下能够共享其部分收集的tell?如果我try之外的runWriterT w似乎被丢弃。如果我尝试进入try,似乎需要MonadUnliftIOMonadUnliftIO听起来可能对我有帮助,但是该软件包说,它只能解除单子语境,而不能解除作家认为的单子状态。有没有人用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")

2 个答案:

答案 0 :(得分: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),而不是因错误而受到严厉惩罚!

随着象牙塔式讲道的进行,我们假设我们确实有

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)

答案 1 :(得分:2)

如果您希望状态在这样的运行时异常中幸免,那么最好的选择是使用可变变量。例如,这是我们在Yesod内部使用的方法。 $colorBatch = array(); foreach ($color as $key => $colorvalue) { $colorBatch[$key] = array( 'id'=>$id 'pro_id' =>$pid, 'color_id' => $colorvalue ); } $this->db->update_batch('product_color', $colorBatch,'pro_id'); 库有一个基于可变引用的rio实例,该实例的工作方式如下:

MonadWriter

在我的演讲“您不想知道的有关monad变压器状态的一切”中,我谈到了这一点(及相关要点):