Как определить лямбда-функцию, которая фильтрует список на основе подтипа типа суммы?
Пример взят из "программирования на 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]