Haskell中的先决条件检查选项有哪些

Haskell中的先决条件检查选项有哪些,haskell,preconditions,Haskell,Preconditions,这是一个简单的问题,我想答案很复杂 一个非常常见的编程问题是一个函数返回某个内容,或未通过前提条件检查。在Java中,我会使用一些断言函数,在方法的开头抛出IllegalArgumentException,如下所示: { //method body Assert.isNotNull(foo); Assert.hasText(bar) return magic(foo, bar); } 我喜欢的是,它是每个前提条件的一行。我不喜欢的是抛出异常(因为exception~goto)

这是一个简单的问题,我想答案很复杂

一个非常常见的编程问题是一个函数返回某个内容,或未通过前提条件检查。在Java中,我会使用一些断言函数,在方法的开头抛出
IllegalArgumentException
,如下所示:

{
  //method body
  Assert.isNotNull(foo);
  Assert.hasText(bar)
  return magic(foo, bar);
}
我喜欢的是,它是每个前提条件的一行。我不喜欢的是抛出异常(因为exception~goto)

在Scala中,我使用过这两种方法,虽然有点笨拙,但总比抛出异常要好

有人向我建议:

putStone stone originalBoard = case attemptedSuicide of 
  True  -> Nothing
  False -> Just boardAfterMove
  where {
    attemptedSuicide = undefined
    boardAfterMove = undefined
  }
我不喜欢的是把重点放在真与假上,它们本身毫无意义;
未遂自杀
先决条件隐藏在语法之间,因此与Nothing没有明确的关联,而
putStone
(boardAfterMove)的实际实现也没有明确的核心逻辑。引导它不会编译,但我确信这不会破坏我问题的正确性


在Haskell中,先决条件检查可以以什么方式干净地完成?

您可以在开始时在模式保护中处理所有先决条件:

putStone stone originalBoard | attemptedSuicide = Nothing
  where attemptedSuicide = ...

putStone stone originalBoard = Just ...

在Haskell中,使用
Maybe
each
比Scala更灵活,因此您可能会重新考虑这种方法。如果你不介意,我将用你的第一个例子来说明这一点

首先,您通常不会测试null。相反,您只需计算您实际感兴趣的属性,使用
Maybe
来处理失败。例如,如果您真正想要的是列表的标题,您可以编写以下函数:

-- Or you can just import this function from the `safe` package

headMay :: [a] -> Maybe a
headMay as = case as of
    []  -> Nothing
    a:_ -> Just a
对于纯粹的验证,如
hasText
,则可以使用
guard
,它适用于任何
MonadPlus
,如
Maybe

guard :: (MonadPlus m) => Bool -> m ()
guard precondition = if precondition then return () else mzero
当您将
guard
专门化为
Maybe
monad时,
return
变成
Just
,而
mzero
变成
Nothing

guard precondition = if precondition then Just () else Nothing
现在,假设我们有以下类型:

foo :: [A]
bar :: SomeForm

hasText :: SomeForm -> Bool

magic :: A -> SomeForm -> B
我们可以处理
foo
bar
的错误,并使用
do
符号为
Maybe
monad安全地提取
magic
函数的值:

example :: Maybe B
example = do
    a <- headMay foo
    guard (hasText bar)
    return (magic a bar)
Maybe
monad中,
(>>=)
return
具有以下定义:

m >>= f = case m of
    Nothing -> Nothing
    Just a  -> f a

return = Just
。。。因此,上述代码只是以下内容的简写:

example = case (headMay foo) of
    Nothing -> Nothing
    Just a  -> case (if (hasText bar) then Just () else Nothing) of
        Nothing -> Nothing
        Just () -> Just (magic a bar)
。。。您可以将其简化为:

example = case (headMay foo) of
    Nothing -> Nothing
    Just a  -> if (hasText bar) then Just (magic a bar) else Nothing
。。。这是您在不使用
do
guard

的情况下手工编写的内容。您有两个选项:

  • 在类型中编码预条件,以便在编译时检查它们
  • 在运行时检查您的先决条件是否成立,以便您的程序在执行令人讨厌和意外的操作之前停止。加布里埃尔·冈萨雷斯详细地展示了这一点
  • 备选案文1。当然是首选,但并不总是可能的。例如,在Haskell的类型系统中,你不能说一个参数大于另一个参数,等等。但是你仍然可以表达很多,通常比其他语言表达的要多得多。还有一些语言使用所谓的and来表示其类型系统中的任何条件。但它们大多是实验或研究工作。若你们感兴趣,我建议你们读亚当·奇利帕拉的书

    运行时检查更容易,程序员也更习惯于这样做。在Scala中,您可以在方法中使用
    require
    ,并从相应的异常中恢复。在哈斯凯尔,这更为棘手。异常(由模式保护失败引起,或由调用
    error
    undefined
    发出)本质上是基于
    IO
    的,因此只有
    IO
    代码才能捕获它们

    如果您怀疑您的代码可能由于某些原因而失败,最好使用
    可能
    向调用者发出失败信号。缺点是这会使代码更复杂,可读性较差


    一种解决方案是将计算嵌入到错误处理/报告monad中,例如。然后,您可以干净地报告错误,并在更高级别的某个位置捕获它们。如果您已经在使用monad进行计算,您可以将monad封装到transformer中。

    我将从更广阔的角度来看待这一点

    在Haskell中,我们通常区分三种类型的函数:

    • Total函数保证为所有参数提供正确的结果。在您的术语中,先决条件编码在类型中。这是最好的函数。其他语言使编写此类函数变得困难,例如,因为无法在类型系统中消除空引用

    • 分部函数保证要么给出正确的结果,要么抛出异常。“头”和“尾”是部分功能。在本例中,您将在Haddock注释中记录前提条件。您不必担心测试前提条件,因为如果您违反它,将抛出异常(尽管有时为了给开发人员提供有用的异常消息,您会进行冗余测试)

    • 不安全的函数会产生损坏的结果。例如,Data.Set模块包含一个函数“fromAscList”,该函数假定其参数已按升序排序。如果违反此前提条件,则会得到一个损坏集,而不是异常。不安全功能应在Haddock注释中明确标记。显然,通过测试前提条件,您总是可以将不安全函数转换为部分函数,但在许多情况下,不安全函数的全部要点是,这对某些客户机来说代价太高,因此您可以向他们提供带有适当警告的不安全函数

    因为Haskell值是不可变的,所以通常不会
    example = case (headMay foo) of
        Nothing -> Nothing
        Just a  -> if (hasText bar) then Just (magic a bar) else Nothing