为haskell中的monad创建过滤器功能

时间:2019-04-07 19:43:17

标签: haskell monads

假设我们具有过滤器功能。

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表示法,但这似乎不是最好的选择,因为存在递归。

2 个答案:

答案 0 :(得分:4)

有两个问题。首先是您已经赋予类型签名:

filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]

,然后使用专门用于filterM的{​​{1}}定义m(因为在定义中使用MaybeJust)。当Haskell推论Nothing必须为m时,将导致错误。 Maybe类型是“刚性的”,因为它是由您以类型签名提供的,刚性类型必须保持通用。如果它们的多态性变得比指定的少,那就是冲突。您可以通过编写以下内容来生成类似的错误消息:

m

很显然,badId :: a -> Int badId x = x a,因此在类型检查过程中确定刚性(程序员指定的)类型Inta匹配就很矛盾。 / 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)但类型专用于特定mMaybe)的类型签名-您已告诉类型检查器函数可以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 <$>)在这里更好,但我个人更愿意将fmapdo一起使用,以使用lambda。

也可以通过排除>>=的重复来简化此操作,并且由于filterM p在对p的调用中不会改变,只需在辅助函数中引用它即可。

filterM