假设我们具有过滤器功能。
filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]
假设我们的a -> m Bool
函数是这样的:
p :: Integer -> Maybe Bool
p n | n > 0 = Just True
| n < 0 = Just False
| otherwise = Nothing
在示例中,m
是Maybe。
我想创建filterM函数,例如:
filterM p [2,−4,1] = Just [2,1]
filterM p [2,0,−4,1] = Nothing
这个filterM本质上是过滤器,但适用于单子。
这是我的实现。它不起作用,对此我有几个问题。
filterM p [] = pure []
filterM p (x : xs) | p x == Just True = x : (filterM p xs)
| p x == Just False = filterM p xs
| otherwise = Nothing
首先,为什么它不起作用。它说Couldn't match type m with Maybe m is a rigid type variable
。
Expected type: m Bool
Actual type: Maybe Bool
在我的函数中,我将“也许”硬编码为m
,但是如何使它更通用?
我会使用do表示法,但这似乎不是最好的选择,因为存在递归。
答案 0 :(得分:4)
有两个问题。首先是您已经赋予类型签名:
filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]
,然后使用专门用于filterM
的{{1}}定义m
(因为在定义中使用Maybe
和Just
)。当Haskell推论Nothing
必须为m
时,将导致错误。 Maybe
类型是“刚性的”,因为它是由您以类型签名提供的,刚性类型必须保持通用。如果它们的多态性变得比指定的少,那就是冲突。您可以通过编写以下内容来生成类似的错误消息:
m
很显然,badId :: a -> Int
badId x = x
是a
,因此在类型检查过程中确定刚性(程序员指定的)类型Int
与a
匹配就很矛盾。 / p>
但是,即使您修复了类型签名,也是如此:
Int
您仍然会收到错误消息。您在代码中混合了单子动作和值。在表达式中:
filterM :: (a -> Maybe Bool) -> [a] -> Maybe [a]
filterM p [] = pure []
filterM p (x : xs) | p x == Just True = x : (filterM p xs)
| p x == Just False = filterM p xs
| otherwise = Nothing
您要将运算符x : filterM p xs
应用于类型(:) :: b -> [b] -> [b]
和a
,因此不会进行类型检查(“无法将预期类型Maybe [a]
与实际类型匹配输入Maybe [a]
。”)
您需要将[a]
替换为以下内容:
x : filterM p xs
答案 1 :(得分:2)
您的类型错误来自指定了多态(m
)但类型专用于特定m
(Maybe
)的类型签名-您已告诉类型检查器函数可以forall m
使用,但不能使用。
要对此进行概括,您需要解决另外两个小问题:首先,您不能在警卫队中使用一般的单子状态,而只能使用纯状态。其次,您不能在:
上使用x
运算符,而不能使用filterM
的结果,因为:
是纯净的,但是您已经给它一个单调的参数。>
对于单声道版本,可以使用do
表示法:
filterM p [] = pure []
filterM p (x : xs) = do
px <- p x -- Run the condition
if px
then do
xs' <- filterM p xs -- Run the recursive action
pure (x : xs') -- Build the result
else filterM p xs
我会使用do表示法,但这似乎不是最好的选择,因为存在递归。
编写带有或不带有do
表示法的递归单子代码是非常好的,正如您在此处看到的那样-您只需要知道何时执行动作与< em>求值表达式。
您的原始代码中的otherwise
情况是由monadic绑定隐式处理的;如果p x
返回Nothing
的{{1}},则整个计算将返回m ~ Maybe
。
如果要避免使用Nothing
表示法,则可以直接使用monad / functor组合器:
do
尽管filterM p [] = pure []
filterM p (x : xs)
= p x >>= \ px -> if px
then (x :) <$> filterM p xs
else filterM p xs
(<$>
)在这里更好,但我个人更愿意将fmap
与do
一起使用,以使用lambda。
也可以通过排除>>=
的重复来简化此操作,并且由于filterM p
在对p
的调用中不会改变,只需在辅助函数中引用它即可。
filterM