如何定义一个 lambda 函数来基于和类型的子类型过滤列表?

4
这个例子出自于《Haskell编程从头开始》。filter函数的目的是除去所有非“DbDate”类型的对象。
在某人的Github上,我发现了使用列表推导和模式匹配(1)进行过滤总和类型的方法。现在我正在尝试使用lambda函数(2)或普通的“case of”或“if then”函数重新定义此过滤器。当涉及自定义数据类型时,我不知道如何正确地检查函数的参数类型。
该书没有向读者介绍任何特定的超级库函数,只介绍了标准的映射、折叠、过滤器以及你会在预处理中找到的其他内容。
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

5
你可能认为这有点学究,但你没有基于类型进行过滤 - Haskell中的所有列表都由同一种类型的元素组成。你想要基于元素的构造函数进行过滤。 - Robin Zigmond
1
那个列表推导式基本上的工作原理是 temp <- dbes, x <- case temp of { DbDate val -> [val]; _ -> [] } - Bergi
3个回答

6
filter的类型为(a -> Bool) -> [a] -> [a],因此它不能改变您列表的类型。
根据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]

4

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

import Data.Maybe

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

就我个人而言,我认为这是写这个函数的第二清晰明了的方式(在列表推导方法之后)。


3

你的确走在了正确的轨道上,因为模式匹配是解决这个问题的一种简单方法,但是由于你的模式匹配不全面,所以你会遇到错误。此外,请注意,如果使用过滤器,仍将得到一个[DatabaseItem]列表,因为过滤器从不改变类型。但是,您可以使用map来完成它。所以:

Case Of

您可以在lambda函数中使用case..of

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

最佳方法

说实话,如果你需要混合使用filter和map,而且你的lambda表达式像这样简单,那么像你的列表推导一样的方式是最干净的:

filterDbDate ds = [d | (DbDate d) <- ds]

你也可以使用foldr代替递归。 - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接