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 оставляет подходящие. Это функции высшего порядка: они принимают другую функцию и заменяют циклы декларативным описанием преобразований.

Проверьте себя
1. Что делает map (* 2) [1,2,3] ?
AСкладывает все элементы
BВозвращает [2,4,6], применив (* 2) к каждому элементу
CОставляет только чётные
DВозвращает число 12
2. Какой тип у функции, передаваемой в filter?
Aa -> b
Ba -> Bool (предикат)
C[a] -> [a]
DInt -> Int