Лямбды и операторы как функции

Лямбда — это функция без имени. А любой оператор в 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 на удивление выразительным.

Проверьте себя
1. Что такое секция (+ 1) в Haskell?
AЧисло 1
BФункция одного аргумента, прибавляющая 1
CСинтаксическая ошибка
DСписок из одного элемента
2. Как записать вызов функции div инфиксно (между аргументами)?
Adiv(10, 3)
B10 .div. 3
C10 `div` 3
D10 ::div 3