检测Haskell中的底部值

检测Haskell中的底部值,haskell,exception-handling,Haskell,Exception Handling,我编写了一个haskell函数,它将列表xs拆分为(init-xs,last-xs),如下所示: split xs = split' [] xs where split' acc (x:[]) = (reverse acc, x) split' acc (x:xs) = split' (x:acc) xs 由于空列表不能以这种方式拆分,因此空列表没有匹配项。但是,我不想简单地错误…函数。因此,我定义如下: split [] = ([], undefine

我编写了一个haskell函数,它将列表
xs
拆分为
(init-xs,last-xs)
,如下所示:

split xs = split' [] xs
    where
        split' acc (x:[]) = (reverse acc, x)
        split' acc (x:xs) = split' (x:acc) xs
由于空列表不能以这种方式拆分,因此空列表没有匹配项。但是,我不想简单地
错误…
函数。因此,我定义如下:

split [] = ([], undefined)
由于延迟评估,因此我可以定义一个安全的
init
,它只返回空列表的空列表:

init' = fst . split
如果我试图访问未定义的文件,有什么方法可以检测到它吗

last' xs
  | isUndefined (snd xs) = ...
  | otherwise            = ...

我确实知道
也许
或者
或者
,而且这些都是表达我想要的更好的选择。但是,我想知道是否有一种方法可以检测未定义的实际值,即捕获错误,如捕获异常。

函数在计算之前不会执行任何操作,因此您可以执行以下操作:

split [] = ([], error "split: empty list")

last' = snd . split

由于底部包含非终止,因此函数
未定义
必须解决停止问题,因此不存在


但是请注意,即使它存在,您仍然无法判断元组的第二个元素中未定义的值是通过
split
函数放在那里的,还是列表的最后一个元素已经未定义。

未定义的
并不比使用
error
更好。事实上,《序曲》中未定义的


现在,一个不会导致
错误的函数称为“total function”,即它对所有输入值都有效

您当前实现的
split
函数具有签名

split :: [a] -> ([a], a)
这是一个问题,因为类型签名承诺结果总是包含一个列表和一个元素,这显然不可能提供泛型类型的空列表

Haskell中解决这个问题的规范方法是更改类型签名,以表示有时第二项没有有效值

split :: [a] -> ([a], Maybe a)
现在,您可以为获得空列表的情况编写适当的实现

split [] = ([], Nothing)
split xs = split' [] xs
    where
        split' acc (x:[]) = (reverse acc, Just x)
        split' acc (x:xs) = split' (x:acc) xs
现在,您可以通过模式匹配来检测缺少值的情况

let (init', last') = split xs
in case last' of
    Nothing -> ... -- do something if we don't have a value
    Just x  -> ... -- do something with value x

Haskell中的错误在语义上等同于⊥ (“底部”)。从技术上讲,它们与非终结语是无法区分的,因此该语言没有检测错误或对错误采取行动的机制


明确地说,
undefined
是一种插入⊥ 在你的程序中,考虑到(正如shang所指出的)
未定义的
是根据
错误定义的,因此,“没有检测或处理
未定义的
的机制”

尽管从语义上讲Ingo的答案是正确的,但如果你使用GHC,有一种方法使用两个“不安全”函数,虽然不是很完美,就像你给它传递了一个IO a类型的计算,它包含一个异常,它将返回真,但它可以工作。不过这有点作弊:)


我知道这很可怕,但它仍然有效。但它不会检测到非终止;)

简单的答案是否定的。不可能检测到未定义的。(一个更复杂的答案是,您可以在IO momad中捕获异常,但这不是您在这里想要做的。)有点讽刺的是,未定义的定义;)@丹伯顿如果这是你的幽默,请注意也可以这样定义
undefined
undefined=undefined
。它的行为不同(此定义在强制时不会终止),但在语义上,它具有相同的值(底部)。虽然从Haskell 2010的角度来看这是正确的,但值得注意的是,在GHC Haskell中,一些⊥ 包括
error
在内的值作为异常实现,这些异常可能在
IO
monad中捕获。然而,这样做通常表明设计不好,因为在纯代码中有更好的方法来处理错误。(甚至还有一个
非终止
异常,当运行时可以检测到非终止时,可能会抛出该异常,尽管由于停止问题,这当然不可靠)。在我看来,这里最好的答案是,因为它回答了scravy无论如何都会遇到的下一个问题:“我应该在
否则
子句中添加什么?”.我确实知道这一点,正如我所说的“感谢懒惰的评估”-将错误放入其中就像将未定义的错误放入其中一样。我想检测这件事,但据我所知,这是不可能的,因为bottom的语义禁止它。+1用于在信息消息中使用
error
。OP选择“我不想<代码>出错…”和使用<代码>未定义的<代码>在调试时通常会导致挫折:“是的,我点击了<代码>未定义的<代码>;但是<代码>未定义的<代码>的哪种用法?”。这在(GHC)Haskell中尤其糟糕,因为懒惰和积极的优化会使编译后的程序以意外的顺序执行。堆栈跟踪最近被添加到GHC中,但它们仍然是选择性加入的,这是一种错觉,而不是二进制文件实际执行情况的真实记录。一开始就告诉我们哪里出了问题要容易得多!这可以通过将“return False”替换为“(\e->return$show e==“Prelude.undefined”)”来改进,在实践中几乎达到100%的准确率(除非有人创建了一个实现show return“Prelude.undefined”的异常,但这将是愚蠢的…
isUndefined“bla”
在我试用过的所有GHC版本(GHC-7.0及以上)上产生一个
内部错误:stg_ap_v_ret
。这里的问题似乎是使用了
unsecfectorce
。如果存在解决停止问题的函数,我们可能会显示False,因此,我们可以显示所有内容,包括“我们可以判断元组的第二个元素中未定义的值是通过split函数放在那里的,还是列表的最后一个元素已经未定义。”
let (init', last') = split xs
in case last' of
    Nothing -> ... -- do something if we don't have a value
    Just x  -> ... -- do something with value x
import Control.Exception
import System.IO.Unsafe
import Unsafe.Coerce

isUndefined :: a -> Bool
isUndefined x = unsafePerformIO $ catch ((unsafeCoerce x :: IO ()) >> return False) (const $ return True :: SomeException -> IO Bool)