Haskell: Return 异常时无

Haskell: Return Nothing in case of exception

{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables #-}

import Data.Typeable
import Control.Exception

data EmptyListException = EmptyListException
    deriving (Show, Typeable)
instance Exception EmptyListException

myHead :: [a] -> a
myHead []    = throw EmptyListException
myHead (x:_) = x

mySafeHead :: [a] -> IO (Maybe a)
mySafeHead xs = (return (Just (myHead xs)))
                `catch`
                (\(ex::EmptyListException) -> return Nothing)

我想要return xs 的第一个元素(如果有的话)。 否则我想 return "Nothing",但它 return 是包裹在 "Just" 中的异常。 这是为什么? P.S.: 我必须在 mySaveHead.

中使用 myHead

您可以使用 evaluate 在执行纯计算时捕获异常:

mySafeHead :: [a] -> IO (Maybe a)
mySafeHead xs = mySafeHead' xs `catch` handler
    where  
        mySafeHead' :: [a] -> IO (Maybe a)
        mySafeHead' ls = do
            x <- evaluate $ myHead ls
            return $ Just x
        handler :: EmptyListException -> IO (Maybe a)
        handler ex = return Nothing

Live demo

所以当我运行你的代码时,这就是我看到的

λ mySafeHead []
Just *** Exception: EmptyListException

我理解您为什么将此描述为 "it returns the Exception wrapped in "只是“.”,但实际上并不是这样。

Haskell 是非严格的,因此它会推迟计算直到需要一个值。

mySafeHead 中,myHead xs 的值未被检查,因此未被评估。相反,该值的计算保留为 thunk,它包装在 Just 中并返回。

然后,在 ghci 中,thunk 在尝试打印值时最终被强制执行,并引发异常。由于我们现在完全超出了 catch 语句的范围,因此它不适用,并且异常一直到终端,在那里它中断了输出的打印。

解决此问题的简单方法是在退出 catch 语句之前使用 seq 强制计算 myHead xs

mySafeHead' :: [a] -> IO (Maybe a)                             
mySafeHead' xs = (let x = myHead xs in x `seq` return (Just x))
                `catch`                                        
                (\(_::EmptyListException) -> return Nothing)   

seq 接受两个参数 - returns 第二个,但仅在将第一个参数强制为弱头范式 (WHNF) 之后,即在找出最外层的构造函数之后。这迫使 x 足以使 EmptyListException 升高,因此 catch 可以做它的事情:

λ mySafeHead' []
Nothing

如果你想要的只是让列表的头部成为 Just a 当 列表是非空的,否则 Nothing,明智的做法是 根本不使用异常(无论是你明确抛出的,还是 由于在空列表上调用 head :: [a] -> a 而抛出)。相反,定义一个 total function:

mySafeHead :: [a] -> Maybe a
mySafeHead [] = Nothing
mySafeHead (a:_) = Just a

或者,使用 safe 包中的 headMay

Prelude 中存在非总函数,例如 headtail 是一个历史错误。希望有一天它们会被弃用并最终被删除。