如何在不经过IO
的情况下在Haskell中使用异常?
我有以下代码,用于在二元搜索树中插入元素,只需最少的比较,当元素是树的成员时不复制。我注意到either
用作catch
而Left
用作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
重写它,希望使代码更简单,但是我弄得一团糟。有什么建议吗?
答案 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 ...