在Haskell中选择列表的头部

时间:2015-11-17 12:20:43

标签: haskell xmonad

现在,我还在学习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来解析字符串列表,但是我无法弄清楚sintax来获得列表的头部......就像这样:

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:再次感谢大家的回答,所有这些都非常有用! :)

3 个答案:

答案 0 :(得分:2)

你只需要改变你在字符串属性上映射的功能; any功能派上用场。而不是

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

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

答案 1 :(得分:1)

如果您已有列表,则无需解析它 - 您只需解构即可。

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. stringProperty "WM_NAME" monad。
  2. 中检索Query
  3. 将任何提供的字符串与其匹配。

    doesNameBeginWithAny :: String -> Query Bool
    doesNameBeginWithAny prefs = do
       wmName <- stringProperty "WM_NAME"
       return $ any (`isPrefixOf`wmName) prefs
    
  4. 这也可以用fmap写成,正如Freerich Raabe所示。

答案 2 :(得分:1)

正如您所知,head是您如何获得列表的第一个元素,但我教给新手的解决方案是使用模式匹配和递归访问列表元素。使用doesNameBeginWith的第一个定义:

doesNameBeginAny :: [String] -> Query Bool
doesNameBeginAny (x:xs) = doesNameBeginWith x <||> doesNameBeginAny xs
doesNameBeginAny [] = return False

在类型声明之后,有两种情况。一个案例采用包含至少一个项目(x)的列表,并在该项目上调用您的第一个函数,将结果与列表其余部分(xs)的递归调用相结合,是空的。模式匹配负责拆分列表以便每次都获得第一项,因此您不需要致电head。第二种情况处理空列表。

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

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

我已使用where定义中间件以使代码更易于遵循。我还使用了foldr而不是foldr1,因此该功能也适用于空列表。同样,您不需要致电head,因为mapfold负责拆分列表。

如果您正在寻找明确区分列表的答案,更像是Python或Java中的for-each循环或其他一些命令式语言,那就不是您编写Haskell的方式。< / p>