map, filter и преобразование данных
Функция высшего порядка — та, что принимает другую функцию как аргумент. map и filter — два главных инструмента такого рода.
Вместо «пройди по списку и сделай с каждым» вы говорите «применить вот эту функцию к каждому». Цикл исчезает, остаётся суть.
Функция высшего порядка (higher-order function) — это функция, принимающая функцию как аргумент или возвращающая функцию. В чистом языке это основной способ выразить «повторение с действием».
map: применить к каждому
map берёт функцию и список, и применяет функцию к каждому элементу, давая новый список:
map :: (a -> b) -> [a] -> [b]
map (* 2) [1, 2, 3] -- [2, 4, 6]
map show [1, 2, 3] -- ["1","2","3"]
map (\x -> x + 1) [10, 20] -- [11, 21]
map (*2) [1, 2, 3]
| | |
*2 *2 *2
| | |
v v v
[2, 4, 6]
Обратите внимание на тип: (a -> b) -> [a] -> [b]. Функция может менять тип (show превращает Int в String), поэтому на входе список a, а на выходе список b.
filter: оставить подходящие
filter берёт предикат (функцию, возвращающую Bool) и оставляет только те элементы, для которых он истинен:
filter :: (a -> Bool) -> [a] -> [a]
filter even [1 .. 10] -- [2,4,6,8,10]
filter (> 3) [1, 5, 2, 8] -- [5, 8]
filter (\x -> x /= 0) xs -- убрать нули
Часто map и filter работают в паре: сначала отобрать, потом преобразовать (или наоборот).
-- удвоить только чётные:
result = map (* 2) (filter even [1 .. 6])
-- filter -> [2,4,6], map -> [4,8,12]
В Python это ровно те же map и filter (или генераторы списков):
# Та же идея на Python: map и filter
nums = [1, 2, 3, 4, 5, 6]
doubled = list(map(lambda x: x * 2, nums))
print(doubled) # [2,4,6,8,10,12]
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens) # [2,4,6]
# в паре: удвоить только чётные
print(list(map(lambda x: x * 2, filter(lambda x: x % 2 == 0, nums)))) # [4,8,12]
Конвейер вместо цикла
Переход от циклов к map и filter — это смена не синтаксиса, а образа мысли. Вы перестаёте думать «пройти по индексам и на каждом шаге что-то сделать» и начинаете думать «применить вот это преобразование ко всем» и «оставить подходящие». Намерение становится видимым с первого взгляда: filter even явно говорит «только чётные», тогда как ручной цикл с условием внутри нужно ещё прочитать и понять. Эти функции прекрасно сочетаются с секциями и частичным применением — map (* 2), filter (> 0) — и выстраиваются в конвейеры через композицию. Важно лишь не путать их роли: map преобразует и сохраняет длину списка, а filter отбирает и может его укоротить. Освоив эту пару, вы покрываете огромную долю повседневной обработки данных, ни разу не написав слово for.
Как это мыслить
Думайте о преобразовании списка как о «конвейере»: данные входят, проходят через filter (отбор) и map (преобразование), выходят готовыми. Вы описываете что сделать с элементами, а не как по ним пройти. Это и короче, и яснее ручного цикла.
Полезно понимать, что map и filter — лишь самые известные представители большого семейства функций высшего порядка. Рядом стоят takeWhile и dropWhile, берущие или отбрасывающие элементы, пока выполняется условие; zipWith, попарно объединяющий два списка заданной функцией; concatMap, сочетающий преобразование с распрямлением вложенных списков. Все они построены на одной идее — передать поведение функцией-аргументом — и комбинируются друг с другом и со свёртками. Освоив базовую пару, вы легко подхватываете остальные, потому что принцип общий. Именно богатство таких комбинаторов делает обработку данных в Haskell настолько лаконичной: почти для любого типичного преобразования уже есть готовая функция высшего порядка, и писать цикл руками практически никогда не приходится.
Частые ошибки
- Путать
mapиfilter.mapпреобразует и сохраняет длину;filterотбирает и может укоротить список. - Передавать в
filterфункцию не наBool. Предикат обязан возвращатьTrue/False. - Лишние лямбды. Вместо
(\x -> x * 2)часто короче секция(* 2).
Best practices
- Предпочитайте
map/filterручной рекурсии — намерение читается мгновенно. - Используйте секции и именованные функции вместо громоздких лямбд.
- Стройте «конвейеры» из нескольких преобразований — следующий урок про их склейку.
Итог. map применяет функцию к каждому элементу, filter оставляет подходящие. Это функции высшего порядка: они принимают другую функцию и заменяют циклы декларативным описанием преобразований.