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中可以干净地完成前置条件检查的方法是什么?
在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)
当你专注guard于Maybe单子,然后return变成Just和mzero变成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)
...这是你可能用手写的没有do或guard.
您有两种选择:
选项1.当然是首选,但并非总是可行.例如,你不能在Haskell的类型系统中说一个参数比其他参数大,等等.但是你仍然可以表达很多,通常比其他语言更多.还有一些语言使用所谓的依赖类型,并允许您在其类型系统中表达任何条件.但它们主要是实验或研究工作.如果您有兴趣,我建议您阅读Adam Chlipala 撰写的" 依赖类型认证编程"一书.
进行运行时检查更容易,这是程序员更习惯的.在Scala中,您可以require在方法中使用并从相应的异常中恢复.在Haskell中这很棘手.例外(由故障模式警卫造成的,或出具致电error或undefined)是由它们的性质IO为主,所以只有IO代码可以抓住他们.
如果您怀疑代码因某些原因而失败,最好使用Maybe或Either向呼叫者发出故障信号.缺点是这会使代码更复杂,更不易读.
一种解决方案是将计算嵌入到错误处理/报告monad中,例如MonadError.然后,您可以干净地报告错误并将其捕获到更高级别的某个位置.如果你已经使用monad进行计算,你可以将你的monad包装成EitherT变换器.
您可以在开头处理模式保护中的所有前提条件:
putStone stone originalBoard | attemptedSuicide = Nothing
where attemptedSuicide = ...
putStone stone originalBoard = Just ...
Run Code Online (Sandbox Code Playgroud)