Как определить лямбда-функцию, которая фильтрует список на основе подтипа типа суммы?

Пример взят из "программирования на Haskell из первых принципов". Цель функции фильтра - избавиться от всех объектов, кроме объектов типа "DbDate".

На GitHub Сомоне я нашел способ фильтрации типов сумм с помощью понимания списка и сопоставления с образцом (1). Теперь я пытаюсь найти способ переопределить этот фильтр с помощью лямбда-функции (2) или обычной функции "case of" 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

3 ответа

filter имеет тип (a -> Bool) -> [a] -> [a] поэтому он не может изменить тип вашего списка.

В соответствии с отчетом Haskell 98 (раздел 3.11) понимание списка используется в коде, который вы нашли на github desugars для:

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

И заменить его на лямбду:

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

Но imho ваше оригинальное решение, использующее понимание списка, выглядит лучше всего:

filterDbDate dbes = [x | (DbDate x) <- dbes]

Как @Niko уже сказал в своем ответе, filter не может изменить тип. Тем не менее, есть вариант filter которые могут: Data.Maybe.mapMaybe :: (a -> Maybe b) -> [a] -> [b], Идея в том, что если вы хотите сохранить элемент, то вы возвращаете Just newvalue из лямбды; в противном случае вы вернетесь Nothing, В этом случае вы могли бы переписать filterDbDate как:

import Data.Maybe

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

Лично я бы сказал, что это второй самый ясный способ написания этой функции (после метода понимания списка).

Вы действительно были на правильном пути, поскольку сопоставление с образцом является простым способом решения этой проблемы, однако вы получите ошибку, поскольку сопоставление с образцом не является исчерпывающим. Также обратите внимание, что если вы используете фильтр, вы все равно получите список [DatabaseItem] поскольку фильтр никогда не меняет тип. Однако вы можете использовать map сделать это. Так:

В случае если

Вы можете иметь 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

Лучший способ

Честно говоря, когда вам нужно смешать фильтр и карту, и ваши лямбды просты, как эта, перечислите понимание, как ваше, - самый чистый способ:

filterDbDate ds = [d | (DbDate d) <- ds]
Другие вопросы по тегам