Haskell中类似于assert(0)的语句是什么?

4

我正在尝试找出如何编写Haskell版本的assert(0)的最佳实践。我知道类型安全要求必须从scanList返回一个整数,但我想知道是否有比我编写的更好的方法。有没有办法避免只是固定在那里的任意数字923

module Main (main) where

import Control.Exception (assert)

l = [
    Left "1",
    Left "1",
    Right 1,
    Right 1,
    Left "9"]

scanList :: [ Either String Int ] -> Int
scanList [    ] = 0
scanList (x:xs) = case x of
    Right i -> i + scanList xs
    Left  s ->
        if read s < 8
        then read s + scanList xs
        else assert False $ 923

main = do
    print $ scanList l

3
我建议您仅在代码中使用断言来表示不变量(即失败表示存在漏洞的情况),它们有助于记录不变量并补充测试。由于在优化编译时将其删除,因此assert调用没有开销。 - jberryman
2个回答

7

来自assert的文档:

如果第一个参数的值为True,则结果为第二个参数。否则,会抛出AssertionFailed异常,其中包含对assert调用的源文件和行号的字符串。

因此,您可以在那里检查您的if条件,而不是给出False作为第一个参数:

scanList (x:xs) = case x of
    Right i -> i + scanList xs
    Left  s ->
        assert (read s < 8) (read s + scanList xs)

4
更符合 Haskell 习惯的设计是让纯函数保持 完全性(total)。当使用 assert 时,你会抛出一个异常,这使得函数变为了 部分(partial)。这意味着您无法再信任函数的类型。尽管它声称具有类型 [Either String Int] -> Int,但在运行时会在各种情况下因异常而失败。
一个完全性函数要么保持在 Either 单子内,或者可以转换为 Maybe
import Text.Read

scanList :: (Num a, Read a, Ord a) => [Either String a] -> Maybe a
scanList [] = Just 0
scanList (x:xs) =
  case x of
    Right i -> fmap (+ i) $ scanList xs
    Left s -> 
      case readMaybe s of
        Just i -> if i < 8 then fmap (+ i) $ scanList xs else Nothing
        Nothing -> Nothing

你可以简化代码,但我选择尽可能保持与原始代码结构相似。
使用类似于 [Either String a] -> Maybe a 这样的类型,任何调用者都知道他们必须处理 Just Nothing 情况,而不必查阅相关函数的代码或文档。

我明白了。那么,在 Haskell 中是否存在某些合理的场景,我们应该使用断言呢?就像你所说的,对于任何返回类型 T 的函数,我总是可以使用 Maybe 来避免使用断言,对吧?那么 Haskell 的设计者为什么要启用这个功能呢? - OrenIshShalom
4
有几个原因导致异常仍然是Haskell语言的一部分。其中之一是Haskell是一种旧的编程语言,某些语言特性还存在于历史上或出于向后兼容的考虑。另一个原因是你可以认为在IO上下文中,异常仍然是适当的,因为函数已经是不纯的。 - Mark Seemann
@OrenIshShalom,断言用于检查您的代码是否正确。在许多情况下,您确实可以通过仔细确保通过结构或类型系统执行不变量来避免它们。但有时您无法这样做,或者不能轻松地这样做。假设您有一棵平衡树。您可以添加一个断言到您的重新平衡操作中,以验证每个结果节点是否实际上适当地平衡。 - dfeuer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接