Лямбды и операторы как функции
Лямбда — это функция без имени. А любой оператор в Haskell — на самом деле обычная функция, которую можно передавать и применять частично.
В Haskell граница между «оператором» и «функцией» почти стёрта.+— это функция, а(+1)— готовая прибавлялка единицы.
Лямбда (анонимная функция) записывается через обратный слеш, который изображает греческую λ:
square :: Int -> Int
square = \x -> x * x
addXY :: Int -> Int -> Int
addXY = \x y -> x + y
Читается так: «бери x, верни x * x». Лямбды удобны, когда функция нужна один раз — например, чтобы передать её в map и не плодить именованных определений.
Операторы — это функции
Оператор вроде + — обычная функция, просто записанная между аргументами (инфиксно). Заключив его в скобки, можно звать как функцию:
-- инфиксно и префиксно — одно и то же
a = 2 + 3
b = (+) 2 3 -- то же самое
-- свою функцию можно звать инфиксно через `обратные кавычки`
c = 10 `div` 3 -- = 3
d = div 10 3 -- то же самое
Секции
Если у оператора частично указать один аргумент, получится секция — готовая функция одного аргумента:
addOne = (+ 1) -- прибавить 1: addOne 4 == 5
half = (/ 2) -- поделить на 2: half 10 == 5.0
tenMinus = (10 -) -- 10 минус что-то: tenMinus 3 == 7
isPos = (> 0) -- больше нуля: isPos (-2) == False
ОПЕРАТОР -> ФУНКЦИЯ -> СЕКЦИЯ 2 + 3 (+) 2 3 (+ 1) ждёт одно число 10 `div` 3 div 10 3 (`div` 2) делит на 2
Секции — крошечные и читаемые функции, идеальные аргументы для map и filter.
В Python лямбды есть, но они ограниченнее; зато идея «оператор как функция» отлично видна через модуль operator:
# Та же идея на Python: лямбды и операторы как функции
square = lambda x: x * x
print(square(5)) # 25
# Оператор + как обычная функция
import operator
print(operator.add(2, 3)) # 5
# Аналог секции (+1) через partial
from functools import partial
add_one = partial(operator.add, 1)
print(add_one(4)) # 5
nums = [1, -2, 3, -4]
print(list(filter(lambda x: x > 0, nums))) # [1, 3]
Стирая границу между данными и операциями
Идея «оператор — это функция» кажется мелочью, но именно она делает Haskell таким гибким. Раз + или * — обычные функции, их можно передавать в другие функции, складывать в списки, частично применять. Секции доводят эту мысль до предела: (* 2), (> 0), (++ "!") — это готовые крошечные функции, рождённые прямо в месте использования. В сочетании с map и filter они дают на удивление выразительный код: map (* 2) . filter (> 0) читается почти как фраза на естественном языке. Лямбды занимают свою нишу — одноразовая функция, которую не стоит называть, — но как только тело лямбды разрастается, лучше дать ей имя через where. Баланс простой: секции и именованные функции для повторяемого, лямбды для мимолётного.
Как это мыслить
Воспринимайте операторы как функции с особым синтаксисом. Тогда становится естественно передавать (+) в другую функцию или делать секцию (* 2) для удвоения. Лямбда — это просто способ сказать «вот функция» прямо в месте использования.
Любопытно, что в Haskell вы можете определять и собственные операторы — не только использовать встроенные. Дав символьное имя своей функции, вы получаете возможность писать её инфиксно, как + или *, и даже задавать ей приоритет и ассоциативность. Этим активно пользуются библиотеки: например, парсер-комбинаторы и веб-фреймворки вводят выразительные операторы, благодаря которым код на них читается почти как предметный язык. Злоупотреблять собственными операторами не стоит — обилие загадочных значков отпугивает читателя, — но сама возможность показывает, насколько в Haskell стёрта граница между «встроенным синтаксисом» и «обычными функциями». Почти всё, что выглядит как часть языка, на деле определено как обычная функция, и вы можете определять такие же.
Частые ошибки
- Секция с минусом.
(- 1)— это число «минус один», а не «вычесть один»; для вычитания пишитеsubtract 1. - Сложные лямбды. Если тело лямбды большое, лучше дать функции имя.
- Путать обратные кавычки и обычные. Инфиксная запись функции — это
`backticks`.
Best practices
- Используйте секции для коротких преобразований:
map (* 2)читается мгновенно. - Лямбды — для одноразовых функций; повторяемое выносите в именованные определения.
- Инфиксная запись через
`делает выражения с двухаргументными функциями читабельнее:x `elem` xs.
Итог. Лямбды дают функции без имени, операторы — это функции в инфиксной записи, а секции вроде (+ 1) и (* 2) — крошечные готовые функции. Вместе это делает Haskell на удивление выразительным.