如何基于单子模式匹配进行过滤?

How to filter based on monadic pattern match?

我有以下问题:

给定一个类型 data T = T (Maybe Int),我如何过滤一个列表以获得非 Nothing 值?

输入

a = [T (Just 3), T Nothing, T (Just 4)]

期望输出

b = [T (Just 3), T (Just 4)]

我试过类似的东西:

b = filter (\x-> x@(Just t)) a 

...认为我可以根据模式匹配进行过滤,但我收到错误:

Pattern syntax in expression context: x@(Just t)
    Did you mean to enable TypeApplications?

稍后能够解压内部值(在 Just 下)并相应地使用它。

我认为在这里我们可以更好地使用列表理解的模式匹配语义:

result = [ e | e@(T (Just _)) <- a]

这里我们枚举a中的元素e,如果与T (Just _)的模式匹配成功,我们在结果列表中yield它。

如果您希望解压封装在T (Just x)中的值,我们可以执行模式匹配,并生成包装元素:

result = [ e | T (Just <b>e</b>) <- a]

因此,这不仅会 "filter" 值,还会同时解包。所以 T Nothing 被忽略,只有包装的 T (Just e) 被保留,相应的 e 最终出现在列表中。

模式匹配仅适用于函数的参数,不适用于主体。您需要匹配的模式是 T,使用像 maybe 这样的变形将包装值转换为布尔值。

Prelude> a = [T (Just 3), T Nothing, T (Just 4)]
Prelude> filter (\(T x) -> maybe False (const True) x) a
[T (Just 3),T (Just 4)]

但是请注意,maybe False (const True) 已定义为 Data.Maybe.isJust

Prelude> import Data.Maybe
Prelude> filter (\(T x) -> isJust x) a
[T (Just 3),T (Just 4)]

如果你有一些 T -> Maybe Int 类型的函数与 isJust 组合,你可以简化谓词。例如:

Prelude> data T = T { getT :: Maybe Int } deriving Show
Prelude> a = [T (Just 3), T Nothing, T (Just 4)]
Prelude> filter (isJust . getT) a
[T {getT = Just 3},T {getT = Just 4}]

如果您想在 T 列表中获取 Int 值的列表(因此键入 [T] -> [Int]),则 mapMaybe 来自 Data.Maybe 几乎完全符合您的要求。除此之外,您只需要一个类型为 T -> Maybe Int

的解包函数
import Data.Maybe ( mapMaybe )

data T = T (Maybe Int)
  deriving (Eq, Show)

unT :: T -> Maybe Int
unT (T x) = x

filterTs = mapMaybe unT

然后:

λ a = [T (Just 3), T Nothing, T (Just 4)]
a :: [T]

λ filterTs a
[3,4]
it :: [Int]

在我看来,将此过滤器操作设为 [T] -> [Int] 类型比将 return 包含非 Nothing 值的 T 值更有用;原因是,即使您将 a 过滤到 [T (Just 3), T (Just 4)],那么稍后处理该问题的代码 仍然 必须在 [=23= 上进行模式匹配] 获取 Int 值,即使您知道永远不会有 Nothing1,因为 T 仍然被硬编码为包含Nothing.

作为一般规则,如果您正在过滤(或默认等)以保证不存在大小写,您应该考虑转换为不再有大小写的类型。它通常使生成的数据更易于处理(例如,不需要模式匹配或 fmaps 进入冗余层),并有助于避免错误。

还有 catMaybes :: [Maybe a] -> [a],这样做 "filtering out Nothings without mapping",但由于您要映射到解包,T 构造函数 mapMaybe 更合适。


1 而这种 "I know there'll never be Nothing here, so I don't have to handle it" 情况是一个非常丰富的 bug 来源,当某些东西改变时隐藏的不变性在未来很容易被破坏。因此,实际编写利用 Nothing "cannot" 存在的知识的代码甚至不是一个好主意;你应该仍然处理这两种情况!