Haskell中的纯异常

时间:2010-10-18 17:12:23

标签: exception haskell monads

如何在不经过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重写它,希望使代码更简单,但是我弄得一团糟。有什么建议吗?

3 个答案:

答案 0 :(得分:8)

这取决于你想要的例外情况。

如果您尝试从函数返回错误值(例如“未找到密钥”或“密钥已存在”),那么您应该沿着这些行使用某些内容。 “左”传统上用于误差值,理由是“右”是正确的结果。 Error monad在这里以与“Maybe”monad相同的方式使用:当发生错误时,其余的计算都没有完成,而你不必连接很多“if then else if then then ....”一起。在这种情况下,“例外”并不是特例;您的代码必须处理它或以某种方式将其传递到下一级别。

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

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

答案 1 :(得分:4)

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

答案 2 :(得分:2)

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

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)

为什么你使用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 ...