【问题标题】:Pure exceptions in HaskellHaskell 中的纯异常
【发布时间】:2010-10-18 17:12:23
【问题描述】:

不经过IO,如何在Haskell中使用异常?

我有以下代码用于在二叉搜索树中插入一个元素,当该元素是树的成员时,比较最少且不复制。我注意到either 用作catchLeft 用作throw

insert x t = either (const t) id (insert' x t Nothing)
    where
    insert' x E m = maybe (Right (T E x E)) (\v -> if x==v then Left E else Right (T E x E)) m
    insert' x t@(T l v r) m = if x<v
                                 then fmap (\l' -> T l' v r) (insert' x l Nothing)
                                 else fmap (\r' -> T l v r') (insert' x r (Just v))

所以我尝试使用Control.Monad.Error 重写它,希望使代码更简单,但我搞砸了。有什么建议吗?

【问题讨论】:

    标签: exception haskell monads


    【解决方案1】:

    这取决于您想要例外的原因。

    如果您尝试从函数返回错误值(例如“未找到密钥”或“密钥已存在”),那么您应该使用这些方面的内容。 “左”传统上用于错误值,因为“右”是正确的结果。 Error monad 在这里的使用方式与“Maybe”monad 相同:当发生错误时,其余的计算不会完成,而无需链接大量“if then else if then ...”。一起。在这种情况下,“例外”并不是真正的例外。您的代码要么必须处理它,要么以某种方式将其传递到下一个级别。

    另一方面,您可能还想捕获不可预见的异常,例如“head []”,您认为某些事情永远不会发生,但您错了。因为这些异常是不可预测的,可能是不确定的,并且通常不适合类型系统,所以它们必须被视为 IO 事件。通常的模式是忽略这些异常,除了程序的最顶层,您可以尝试保存用户的工作并提供有用的消息,例如“请报告此错误”。

    抛出后一种异常很容易:只需调用“错误”即可。但只将它用于您真正认为不可能发生的事情;它不应该是你代码的正常部分。

    【讨论】:

    • 还可以查看 Real World Haskell 书中的 Error Handling 章节。
    • 吹毛求疵:如果你不使用Left 处理错误情况,你就不能将Either 做成仿函数、monad 等,这才是更重要的;不只是的两个意思。
    • @Antal S-Z:就我个人而言,我认为这只是因为the left-hand side is evil
    【解决方案2】:

    Hackage 上的 monadLib 包有一个 Exception monad(和一个 ExceptionT monad 转换器),您可以在没有 IO 的情况下使用它。当你运行它时,你会得到一个 Either 类型的结果。

    【讨论】:

      【解决方案3】:

      棘手! 这是一个非常好的机制,可以在最后一刻将值与 (==) 进行比较,并且仅当需要时。 Byt 你为什么不至少用类型信息来评论它?

      data Tree a = E | T (Tree a) a (Tree a)
      
      insert :: (Ord a) => a -> Tree a -> Tree a
      insert x t = const t `either` id $ insert' x t Nothing
          where
          -- insert' (insert_this) (into_this_empty_tree) (except_if_it_equals_this) (because_then_the_tree_is_Left_unchanged)
          insert' :: (Ord a) => a -> Tree a -> Maybe a -> Either (Tree a) (Tree a)
          insert' x E Nothing = Right (T E x E)
          insert' x E (Just v) | x==v      = Left E
                               | otherwise = Right (T E x E)
          -- insert' (insert_this) (into_this_nonempty_tree) ((anyway)) (recursive:if_it_branches_to_the_left_insert_it_there)
          -- insert' (insert_this) (into_this_nonempty_tree) ((anyway)) (recursive:if_it_equals_or_branches_to_the_right_insert_it_there_except_if_the_right_branch_is_empty)
          insert' x t@(T l v r) _ | x<v       = (\l' -> T l' v r) `fmap` insert' x l Nothing
                                  | otherwise = (\r' -> T l v r') `fmap` insert' x r (Just v)
      

      如果你把 Left 的箱子扔掉然后使用副本,你为什么要使用 Either?如果您不保留该副本来替换相等的树,而是根本不构建相等的树,那么效率会更高。好像是这样的……

      insert' :: (Ord a) => a -> Tree a -> Maybe a -> Maybe (Tree a)
      

      然后......如果你想真正高效,不要构建那个(也许是一个)参数只是为了在之后进行比较。

      --insert'1 :: (Ord a) => a -> Tree a -> Nothin -> Maybe (Tree a)
      --insert'2 :: (Ord a) => a -> Tree a -> Just a -> Maybe (Tree a)
      insert'1 :: (Ord a) => a -> Tree a -> Maybe (Tree a)
      insert'2 :: (Ord a) => a -> Tree a -> a -> Maybe (Tree a)
      

      解决方案如下所示:

      insert :: (Ord a) => a -> Tree a -> Tree a
      insert x t = fromMaybe t $ insert'1 x t
          where
          insert'1 :: (Ord a) => a -> Tree a -> Maybe (Tree a)
          insert'2 :: (Ord a) => a -> Tree a -> a -> Maybe (Tree a)
          insert'1 x E = Just (T E x E)
          insert'1 x (T l v r) | x<v       = do l' <- insert'1 x l
                                                Just (T l' v r)
                               | otherwise = do r' <- insert'2 x r
                                                Just (T l v r')
          insert'2 x E v = guard (x/=v) >> Just (T E x E)
          insert'2 x t _ = insert'1 x t
      

      (编辑:)

      在 Control.Monad.Error 中定义了这个实例:

      Error e => MonadError e (Either e)
      

      这意味着,(任一字符串)可能就是您要查找的内容。

      insert :: (Ord a,MonadError String m) => a -> Tree a -> m (Tree a)
      insert x t = maybe (throwError "Error: element already in tree") return $ insert'1 x t
          where ...
      

      【讨论】:

      • 其实我从insert'1insert'2insert'的方式完全相反。你是绝对正确的,我可以使用Maybe 作为insert' 的结果。但接下来的问题是如何将Nothing 替换为throwmaybe 替换为catch
      • if_it_equals_or_branches_to_the_right_insert_it_there_except_if_the_right_branch_is_empty 这是我见过的最长的标识符。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-11
      • 2011-10-30
      相关资源
      最近更新 更多