在递归函数中使用“Either”进行错误处理

时间:2011-11-09 05:38:47

标签: haskell error-handling binary-tree

假设有一个二叉搜索树,我想在我们尝试插入已经存在的元素时返回错误。有没有办法使这项工作?

data BST2 a = EmptyBST2 | Node2 a (BST2 a) (BST2 a)  deriving Show

insert2 :: a -> Either b (BST2 a) -> Either b (BST2 a)
insert2 elem (Right EmptyBST2) = Right (Node2 elem EmptyBST2 EmptyBST2)
insert2 elem (Right (Node2 root left right))
  | (elem == root) = Left "Error: Element already exist."
  | (elem < root) = (Node2 root (insert2 elem left) right)
  | otherwise = (Node2 root left (insert2 elem right))

注意:我是Haskell的新手。

3 个答案:

答案 0 :(得分:5)

@Andre只是试图为您的代码提供最小的修复。在Haskell中实现错误处理任务的惯用方法是使用Error monad。主要原因是可能重用liftM2库函数来实现combinethrowErrorreturn可以替换为LeftRight,但通用函数可以更清楚地说明代码的用途。

module Err where

import Control.Monad (liftM2)
import Control.Monad.Error (throwError)

data BST2 a = EmptyBST2 | Node2 a (BST2 a) (BST2 a)  deriving Show

combine root = liftM2 (Node2 root)

insert2 :: (Ord a) => a -> BST2 a -> Either String (BST2 a)
insert2 elem EmptyBST2 = return $ Node2 elem EmptyBST2 EmptyBST2
insert2 elem (Node2 root left right)
  | (elem == root) = throwError "insert2 error: Element already exists."
  | (elem < root) = combine root (insert2 elem left) (return right)
  | otherwise = combine root (return left) (insert2 elem right)

请注意,combine可以更短:combine = liftM2 . Node2或更长:combine root left right = liftM2 (Node2 root) left right。使用您最了解的风格。

还有一些关于错误@Andre修复的评论:

  • insert2在错误类型中不是多态的 - 如果失败,它总是返回String。所以他在类型声明中使用String而不是b
  • 与列表不同,有序集合不能存储任何类型 - 只能将可比较(有序)的类型放入树中。因此,他在树值类型上添加了Ord a =>约束,以表明必须为该类型实施<==
  • insert2返回Either。您尝试将LeftRight传递给Node2Node2 root (Left foo) right失败,因为它预计会Node2 a,但会提供Either String (Node2 a)

最后,使用throwErrorreturn的另一个原因是该函数变得通用:

insert2 :: (Ord a, MonadError String m) => a -> BST2 a -> m (BST2 a)

并且您可以将其用于除MonadError之外的Either个实例,但您需要在{-# LANGUAGE FlexibleContexts #-}声明之前在源文件的顶部添加module pragma

答案 1 :(得分:3)

快速解决方案(不一定简单):

data BST2 a = EmptyBST2 | Node2 a (BST2 a) (BST2 a)  deriving Show

combine :: a -> Either b (BST2 a) -> Either b (BST2 a) -> Either b (BST2 a)
combine a (Left b) _ = Left b
combine a _ (Left b) = Left b
combine a (Right left_subtree) (Right right_subtree) = Right (Node2 a left_subtree right_subtree)

insert2 :: (Ord a) => a -> Either String (BST2 a) -> Either String (BST2 a)
insert2 elem (Right EmptyBST2) = Right (Node2 elem EmptyBST2 EmptyBST2)
insert2 elem (Right (Node2 root left right))
  | (elem == root) = Left "Error: Element already exist."
  | (elem < root) = combine root (insert2 elem (Right left)) (Right right)
  | otherwise = combine root (Right left) (insert2 elem (Right right))

-- test data
t1 = EmptyBST2
t2 = Node2 17 t1 t1
t3 = Node2 42 t2 t1
t4 = insert2 11 (Right t3)
t5 = insert2 17 (Right t3)

答案 2 :(得分:2)

使用辅助函数解决此问题更简单:

insert2 :: (Ord a) => a -> BST2 a -> Either String (BST2 a)
insert2 newVal tree
  | contains newVal tree = Left "Error:  element already in tree"
  | otherwise = Right $ insert newVal tree

现在您需要containsinsert

contains :: (Ord a) => a -> BST2 a -> Bool
contains .... implementation .... -- checks whether a BST2 contains an element

insert :: (Ord a) => a -> BST2 a -> BST2 a
insert .... implementation .... -- inserts an element if not already there,
                                --   otherwise returns original tree

或者,您可以使用Haskell's many other approaches之一来代替Either String (BST2 a)而不是{{1}}。