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