5

该示例取自“Haskell programming from first principle”过滤器功能的目标是摆脱除“DbDate”类型之外的所有对象。

在 somone 的 github 上,我找到了一种使用列表理解和模式匹配 (1) 过滤总和类型的方法。现在我正在尝试找到一种方法来使用 lambda 函数(2)或“if then”函数的正常“case of”重新定义此过滤器。当我处理自定义数据类型时,我不知道如何正确检查函数的参数类型。

本书没有向读者介绍任何超级特定的库函数,只是标准的地图、折叠、过滤器和其他你会在序曲中找到的东西。

import Data.Time

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

--List that needs to be filtered
theDatabase :: [DatabaseItem]
theDatabase =
  [ DbDate (UTCTime (fromGregorian 1911 5 1)
                    (secondsToDiffTime 34123))
  , DbNumber 9001
  , DbString "Hello, world!"
  , DbDate (UTCTime (fromGregorian 1921 5 1)
                    (secondsToDiffTime 34123))
  ]



--1 works fine, found on someone's git hub
filterDbDate :: [DatabaseItem] -> [UTCTime]
filterDbDate dbes = [x | (DbDate x) <- dbes]

--2 Looking for the eqivalents with lambda or "case" or "if then"
--pattern is not satisfactory

filterDbDate :: [DatabaseItem] -> [UTCTime]
filterDbDate dbes = filter (\(DbDate x) -> True) theDatabase
4

3 回答 3

6

filter具有类型(a -> Bool) -> [a] -> [a],因此它无法更改列表的类型。

根据The Haskell 98 报告(第 3.11 节),您在 github 上找到的代码中使用的列表理解去糖:

filterDbDate2 :: [DatabaseItem] -> [UTCTime]
filterDbDate2 dbes = let extractTime (DbDate time) = [time]
                         extractTime _             = []
                     in concatMap extractTime theDatabase

您可以重写extractTime以使用case ... of

filterDbDate3 :: [DatabaseItem] -> [UTCTime]
filterDbDate3 dbes = let extractTime item = case item of (DbDate time) -> [time]
                                                         _             -> []
                     in concatMap extractTime theDatabase

并用 lambda 替换它:

filterDbDate4 :: [DatabaseItem] -> [UTCTime]
filterDbDate4 dbes = concatMap (\item -> 
    case item of 
        (DbDate time) -> [time]
        _             -> []) 
    theDatabase

但是恕我直言,您使用列表理解的原始解决方案看起来最好:

filterDbDate dbes = [x | (DbDate x) <- dbes]
于 2019-02-27T21:35:42.547 回答
4

正如@Niko 在他的回答中已经说过的那样,filter不能改变类型。但是,有一个变体filter可以:Data.Maybe.mapMaybe :: (a -> Maybe b) -> [a] -> [b]. 这个想法是,如果你想保留一个元素,那么你Just newvalue从 lambda 中返回;否则你返回Nothing。在这种情况下,您可以重写filterDbDate为:

import Data.Maybe

filterDbDate dbes = mapMaybe (\x -> case x of { DBDate d -> Just d; _ -> Nothing }) dbes

就个人而言,我会说这是编写此函数的第二个最清晰的方法(在列表理解方法之后)。

于 2019-02-27T22:14:14.310 回答
3

您确实走在正确的轨道上,因为模式匹配是解决此问题的一种简单方法,但是由于模式匹配不全面,您会遇到错误。另外,请注意,如果您使用过滤器,您仍然会得到一个列表,[DatabaseItem]因为过滤器永远不会更改类型。但是,您可以使用map它来执行此操作。所以:

案例

你可以case .. of在你的 lambda 函数中有一个:

filterDbDate' :: [DatabaseItem] -> [UTCTime]
filterDbDate' = map (\(DbDate x) -> x) .filter (\x ->
  case x of
    DbDate x -> True
    _        -> False)

递归+模式匹配

但是我认为使用递归更清楚:

filterDbDate'' :: [DatabaseItem] -> [UTCTime]
filterDbDate'' [] = []
filterDbDate'' ((DbDate d):ds) = d : filterDbDate ds
filterDbDate'' (_:ds)          =     filterDbDate ds

最好的办法

老实说,当您必须混合过滤器和映射时,并且您的 lambda 表达式像这样简单,像您这样的列表推导式是最干净的方法:

filterDbDate ds = [d | (DbDate d) <- ds]
于 2019-02-27T20:49:49.357 回答