Select Haskell 列表的头部

Select head of a list in Haskell

现在,我仍在学习 haskell(配置我的 xmonad wm),请耐心等待。

我写过这个函数:

doesNameBeginWith :: String -> Query Bool
doesNameBeginWith str = fmap ( str `isPrefixOf`) (stringProperty "WM_NAME")  

这将根据我的 str 检查 WM_NAME X 属性 的开始。我可以这样写:

isMusic = doesNameBeginWith "MPD:" <||> doesNameBeginWith "ncmpcpp"

有效。现在我想用这个签名写一个函数:

[String] -> Query Bool

这样我就可以这样称呼它了:

isMusic = doesNameBeginWith ["MPD:", "ncmpcpp"]

我的想法是使用 foldr1 来解析字符串列表,但我无法弄清楚获取列表头部的语法...像这样:

doesNameBeginWith :: [String] -> Query Bool
doesNameBeginWith str = foldr1 (<||>) . fmap ( (head str) `isPrefixOf`) (stringProperty "WM_NAME") 

编辑 1:按照 leftaroundabout 的建议,我可以组合两个函数来获得我想要的:

doesNameBeginWithL :: [String] -> Query Bool
doesNameBeginWithL = foldr1 (<||>) . map doesNameBeginWith

但我仍然希望有一种更直接的方式,并希望了解在这种情况下如何使用列表头!

编辑 2:再次感谢大家的回答,他们都提供了很多信息! :)

如果你已经有一个列表,你不需要解析它——你可以简单地解构它。

isThisStuff :: [String] -> Query Bool
isThisStuff [c,d] = doesNameBeginWith c <||> doesNameBeginWith d

然后

isMusic = isThisStuff ["MPD:", "ncmpcpp"]

请注意,如果提供的列表没有正好包含两个元素,此操作将失败。

可以说,基于折叠的解决方案 更好。正如您所指出的,这可以写成

foldr1 (<||>) . map doesNameBeginWith

现在,要摆脱其中的 doesNameBeginWith,您需要做的就是 内联 它。注意

doesNameBeginWith = \str -> fmap ( str `isPrefixOf`) (stringProperty "WM_NAME")

所以你可以使用

foldr1 (<||>) . map (\str -> fmap ( str `isPrefixOf`) (stringProperty "WM_NAME"))

如果你不喜欢 lambda,你可以将其表述为列表理解:

doesNameBeginWithAny strs = foldr1 (<||>)
     [fmap ( str `isPrefixOf`) (stringProperty "WM_NAME") | str <- strs]

或者,您可以使 doesNameBeginWith 免点

doesNameBeginWith = \str -> fmap (isPrefixOf str) (stringProperty "WM_NAME")            
                  = \str -> flip fmap (stringProperty "WM_NAME") (isPrefixOf str)
                  = \str -> flip fmap (stringProperty "WM_NAME") . isPrefixOf $ str
                  = flip fmap (stringProperty "WM_NAME") . isPrefixOf
                  = (stringProperty "WM_NAME" `fmap`) . isPrefixOf

所以

doesNameBeginWithAny = foldr1 (<||>)
                     . map ((stringProperty "WM_NAME" `fmap`) . isPrefixOf)

不过

所有这些都太复杂了。当你拆分问题时,它会变得更简单:

  1. Query monad 中检索 stringProperty "WM_NAME"
  2. 用它匹配任何提供的字符串。

    doesNameBeginWithAny :: String -> Query Bool
    doesNameBeginWithAny prefs = do
       wmName <- stringProperty "WM_NAME"
       return $ any (`isPrefixOf`wmName) prefs
    

这也可以用 fmap 编写,如 Freerich Raabe 所示。

如您所知,head 是获取列表第一个元素的方法,但我教给新手的解决方案是使用 pattern-matching 和递归访问列表元素。使用 doesNameBeginWith:

的第一个定义
doesNameBeginAny :: [String] -> Query Bool
doesNameBeginAny (x:xs) = doesNameBeginWith x <||> doesNameBeginAny xs
doesNameBeginAny [] = return False

类型声明后,有两种情况。一种情况采用包含至少一个项目 (x) 的列表,并在该项目上调用您的第一个函数,将结果与对列表其余部分的递归调用结合起来 (xs,这可能为空)。模式匹配负责每次将列表拆开得到第一项,所以你不需要调用head。第二种情况处理空列表。

正如您已经发现的那样,函数 foldr1(以及其他折叠)抽象出函数的这种结构。您可以使用 map 将列表转换为 Query Bool 的列表,然后使用 fold 通过将元素组合在一起将列表减少为单个项目。

doesNameBeginAny xs = foldr (<||>) False queries
    where queries = map doesNameBeginWith xs

我已经使用 where 定义了一个中间体,使代码更容易理解。我还使用了 foldr 而不是 foldr1,因此该函数也适用于空列表。同样,您不需要调用 head,因为 mapfold 负责拆分列表。

如果你正在寻找一个明确地将列表分开的答案,更像是 Python 或 Java 或其他命令式语言中的 for-each 循环,那不是你的方式写 Haskell.

您只需要稍微更改一下映射到字符串 属性 上的函数; any 函数派上用场了。而不是

doesNameBeginWith str = fmap ( str `isPrefixOf`) (stringProperty "WM_NAME")  

尝试

doesNameBeginWith xs  = fmap (\x -> any (`isPrefixOf` x) xs) (stringProperty "WM_NAME")