Haskell中的前提条件检查有哪些选项

iwe*_*ein 4 haskell preconditions

这是一个简单的问题,我认为这是一个复杂的答案.

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

{
  //method body
  Assert.isNotNull(foo);
  Assert.hasText(bar)
  return magic(foo, bar);
}
Run Code Online (Sandbox Code Playgroud)

我喜欢这个是它是每个先决条件的oneliner.我不喜欢这个是抛出异常(因为异常~goto).

在Scala中,我使用了Either,它有点笨重,但比抛出异常更好.

有人向我建议:

putStone stone originalBoard = case attemptedSuicide of 
  True  -> Nothing
  False -> Just boardAfterMove
  where {
    attemptedSuicide = undefined
    boardAfterMove = undefined
  }
Run Code Online (Sandbox Code Playgroud)

我不喜欢的是强调真假,这本身就没有任何意义; 的attemptedSuicide前提是藏在语法之间,所以没有明确相关的任何操作,实际执行的putStone(boardAfterMove)是没有明确的核心逻辑.要启动它不会编译,但我确信这不会破坏我的问题的有效性.

在Haskell中可以干净地完成前置条件检查的方法是什么?

Gab*_*lez 7

在Haskell中,与Scala 一起使用Maybe并且Either比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
Run Code Online (Sandbox Code Playgroud)

对于纯粹验证的东西,比如hasText,你可以使用guard,适用于任何MonadPlus类似Maybe:

guard :: (MonadPlus m) => Bool -> m ()
guard precondition = if precondition then return () else mzero
Run Code Online (Sandbox Code Playgroud)

当你专注guardMaybe单子,然后return变成Justmzero变成Nothing:

guard precondition = if precondition then Just () else Nothing
Run Code Online (Sandbox Code Playgroud)

现在,假设我们有以下类型:

foo :: [A]
bar :: SomeForm

hasText :: SomeForm -> Bool

magic :: A -> SomeForm -> B
Run Code Online (Sandbox Code Playgroud)

我们可以处理两者的错误,foo并使用monad的表示法为函数bar安全地提取值:magicdoMaybe

example :: Maybe B
example = do
    a <- headMay foo
    guard (hasText bar)
    return (magic a bar)
Run Code Online (Sandbox Code Playgroud)

如果你熟悉Scala,那么do符号就像Scala一样用于理解.以上代码涉及:

example =
    headMay foo >>= \a ->
      guard (hasText bar) >>= \_ ->
        return (magic a bar)
Run Code Online (Sandbox Code Playgroud)

Maybe单子,(>>=)return具有以下定义:

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

return = Just
Run Code Online (Sandbox Code Playgroud)

...所以上面的代码只是简单的:

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)
Run Code Online (Sandbox Code Playgroud)

......你可以简化为:

example = case (headMay foo) of
    Nothing -> Nothing
    Just a  -> if (hasText bar) then Just (magic a bar) else Nothing
Run Code Online (Sandbox Code Playgroud)

...这是你可能用手写的没有doguard.


Pet*_*lák 6

您有两种选择:

  1. 在您的类型中编码您的前提条件,以便在编译时检查它们.
  2. 在运行时检查您的先决条件是否保持,以便您的程序在做出令人讨厌和意外的事情之前停止.Gabriel Gonzales详细说明了他的回答

选项1.当然是首选,但并非总是可行.例如,你不能在Haskell的类型系统中说一个参数比其他参数大,等等.但是你仍然可以表达很多,通常比其他语言更多.还有一些语言使用所谓的依赖类型,并允许您在其类型系统中表达任何条件.但它们主要是实验或研究工作.如果您有兴趣,我建议您阅读Adam Chlipala 撰写的" 依赖类型认证编程"一书.

进行运行时检查更容易,这是程序员更习惯的.在Scala中,您可以require在方法中使用并从相应的异常中恢复.在Haskell中这很棘手.例外(由故障模式警卫造成的,或出具致电errorundefined)是由它们的性质IO为主,所以只有IO代码可以抓住他们.

如果您怀疑代码因某些原因而失败,最好使用MaybeEither向呼叫者发出故障信号.缺点是这会使代码更复杂,更不易读.

一种解决方案是将计算嵌入到错误处理/报告monad中,例如MonadError.然后,您可以干净地报告错误并将其捕获到更高级别的某个位置.如果你已经使用monad进行计算,你可以将你的monad包装成EitherT变换器.


Joa*_*ner 5

您可以在开头处理模式保护中的所有前提条件:

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

putStone stone originalBoard = Just ...
Run Code Online (Sandbox Code Playgroud)