如何从Haskell中的列表中仅获取特定类型的元素?

Emr*_*inç 8 haskell

我正在研究Haskell Book,在第10章(折叠列表)中,我正在尝试解决关于从包含不同类型元素的列表中仅获取一种特定类型元素的练习.

作者给出了以下代码:

import Data.Time

data DatabaseItem = DbString String
                  | DbNumber Integer
                  | DbDate   UTCTime
                  deriving (Eq, Ord, Show)

theDatabase :: [DatabaseItem]
theDatabase = [ DbDate (UTCTime
                        (fromGregorian 1911 5 1)
                        (secondsToDiffTime 34123))
              , DbNumber 9001
              , DbString "Hello, world!"
              , DbDate (UTCTime
                        (fromGregorian 1921 5 1)
                        (secondsToDiffTime 34123))
              ]
Run Code Online (Sandbox Code Playgroud)

第一个问题是:

编写一个过滤DbDate值的函数,并返回其中的UTCTime值列表.

filterDbDate :: [DatabaseItem] -> [UTCTime]
filterDbDate = undefined
Run Code Online (Sandbox Code Playgroud)

由于本章是关于折叠列表的,所以我认为可以使用,例如,foldr.

我最初的尝试是首先编写一些辅助函数并在a中使用它们foldr,例如:

getDbDate1 :: DatabaseItem -> UTCTime
getDbDate1 (DbDate utcTime) = utcTime

isDbDate :: DatabaseItem -> Bool
isDbDate (DbDate _) = True
isDbDate _ = False

filterDbDate1 :: [DatabaseItem] -> [UTCTime]
filterDbDate1 database = foldr ((:) . getDbDate1) [] (filter isDbDate database)
Run Code Online (Sandbox Code Playgroud)

这似乎可以完成这项工作,因为:

?> filterDbDate1 theDatabase
[1911-05-01 09:28:43 UTC,1921-05-01 09:28:43 UTC]
Run Code Online (Sandbox Code Playgroud)

但我对这个解决方案不满意,因为首先,它给出了以下警告:

/Users/emre/code/haskell/chapter10_folding_lists/database.hs:36:1: Warning: …
    Pattern match(es) are non-exhaustive
    In an equation for ‘getDbDate1’:
        Patterns not matched:
            DbString _
            DbNumber _
Run Code Online (Sandbox Code Playgroud)

我正在使用两个辅助函数,一个用于帮助过滤掉不是DbDate的值,另一个用于获取UTCTime组件.

因此,为了摆脱非详尽的模式匹配警告并使用单个辅助函数,我决定将它写成:

getDbDate2 :: DatabaseItem -> Maybe UTCTime
getDbDate2 (DbDate utcTime) = Just utcTime
getDbDate2 _ = Nothing

filterDbDate2 :: [DatabaseItem] -> [UTCTime]
filterDbDate2 database = foldr ((:) . getDbDate2) [] database
Run Code Online (Sandbox Code Playgroud)

但是,当然,上面没有编译,因为它没有类型检查,因为,例如:

?> foldr ((:) . getDbDate2) [] theDatabase
[Just 1911-05-01 09:28:43 UTC,Nothing,Nothing,Just 1921-05-01 09:28:43 UTC]
Run Code Online (Sandbox Code Playgroud)

换句话说,它可以返回Just UTCTime值列表以及值,Nothing而不仅仅是UTCTime值列表.

所以我的问题是:如何编写一个(helper?)函数,一次性(这样我就不必使用它filter),检查它的值是否为DbNumber,如果是,则返回UTCTime组件?(如果不是......它也必须返回一些东西(例如Nothing?),这就是我遇到麻烦,即使用Maybe UTCTime,然后获取Just UTCTime值等等)

Dan*_*ner 11

这里还有其他几个答案,涵盖了关于其他思考问题的方法的好建议:catMaybes在选择Maybe UTCTimes 之后第二次使用来挖掘数据; 使用列表推导和他们用来过滤掉不匹配模式的方便语法; 使用列表的monadic结构来包含或跳过结果; 并编写一个定制的递归函数.在这个答案中,我将解决你的直接问题,展示如何使用你已经拥有的程序结构,而不必完全重新思考列表操作的方法 - foldr使用辅助函数调用,一次性完成所需的一切.

首先,我观察到你所有现有的尝试都发送foldr了一个无条件调用的函数(:):

foldr ((:) . getDbDate1) [] (filter isDbDate database)
foldr ((:) . getDbDate2) [] database
Run Code Online (Sandbox Code Playgroud)

关于这种模式的事情是,这意味着你从foldr遗嘱中获得的列表与你传入的函数具有相同的长度 - 因为(:)输入列表中的每个都变成(:)了输出列表中的a.在您的第一个解决方案中,您通过从输入列表中删除了一些您不关心的条目来处理此问题; 在第二个解决方案中,您通过在输出列表中添加了额外的无趣元素来处理此问题.

第三种解决方案是在决定是否调用之前查看列表元素(:).以下是一个人可能会这样做:

conditionalCons :: DatabaseItem -> [UTCTime] -> [UTCTime]
conditionalCons (DbDate t) ts = t:ts
conditionalCons _          ts =   ts
Run Code Online (Sandbox Code Playgroud)

请特别注意,在第二个子句中,我们不调用(:)- 这会过滤掉列表中不匹配的元素.我们也不担心缺少模式.现在我们可以写了

filterDbDate3 :: [DatabaseItem] -> [UTCTime]
filterDbDate3 = foldr conditionalCons []
Run Code Online (Sandbox Code Playgroud)

在ghci中测试它:

> filterDbDate3 theDatabase
[1911-05-01 09:28:43 UTC,1921-05-01 09:28:43 UTC]
Run Code Online (Sandbox Code Playgroud)

完善!


Fra*_*nky 8

一个简单的列表理解就可以了

filterDbDate xs = [ x | DbDate x <- xs ]
Run Code Online (Sandbox Code Playgroud)

  • @ Code-Apprentice,这个列表理解仍然有效. (2认同)
  • 列表理解只是monadic计算的语法糖.当`DbDate x`匹配时,它返回`[x]`; 否则,`Monad`类的`fail`函数返回一个空列表.每个列表的串联产生最终列表,仅包含来自`DbDate`值的`x`s. (2认同)