Конвейер |> и композиция >>

Два оператора, формирующие стиль F#: конвейер |> для данных и композиция >> для функций.

Оператор конвейера |> передаёт значение слева как последний аргумент функции справа: x |> f равно f x.

Зачем конвейер

Без конвейера вложенные вызовы читаются «изнутри наружу»: List.sum (List.map square (List.filter even xs)). Это тяжело читать. Конвейер разворачивает поток данных слева направо, как мы и думаем о преобразованиях.

let xs = [1; 2; 3; 4; 5; 6]
let result =
    xs
    |> List.filter (fun n -> n % 2 = 0)
    |> List.map (fun n -> n * n)
    |> List.sum
printfn "%d" result

Вывод:

56

Читается как рассказ: взять xs, отфильтровать чётные ([2;4;6]), возвести в квадрат ([4;16;36]), сложить (56). Каждый шаг получает результат предыдущего.

Почему именно «последний аргумент»

Функции коллекций в F# спроектированы так, что обрабатываемые данные — последний параметр (List.map f список). А |> подставляет значение именно последним. В сочетании с частичным применением (List.map square — это функция от списка) конвейер складывается идеально.

Композиция функций >>

Оператор >> склеивает две функции в одну: (f >> g) x равно g (f x) — сначала f, потом g. Разница с конвейером: |> работает со значениями, >> — с функциями, создавая новую функцию без упоминания аргумента.

let addOne x = x + 1
let double x = x * 2
let addThenDouble = addOne >> double   // сначала +1, потом *2
printfn "%d" (addThenDouble 5)

Вывод:

12

5 + 1 = 6, затем 6 * 2 = 12. Получилась новая функция addThenDouble без явного аргумента — это называют «point-free» стилем.

Обратные операторы

Есть и зеркальные версии: <| (обратный конвейер, f <| x = f x, иногда экономит скобки) и << (обратная композиция, g << f = сначала f). Используются реже; основной рабочий инструмент — |>.

Как работает под капотом

Оба оператора — это обычные функции из стандартной библиотеки, определённые элементарно: let (|>) x f = f x и let (>>) f g x = g (f x). Никакой магии компилятора — это просто инфиксные функции. Поэтому их можно переопределять и понимать как обычный код. Компилятор инлайнит их, так что накладных расходов в рантайме нет.

Частые ошибки

  • Путать |> (значение в функцию) и >> (функция в функцию).
  • Думать, что >> применяет функции справа налево — нет, f >> g — сначала f.
  • Переусердствовать с point-free стилем — иногда явный аргумент читается понятнее.

Итоги

  • x |> f = f x — конвейер выстраивает поток данных слева направо.
  • Функции коллекций берут данные последним аргументом — это и делает |> удобным.
  • f >> g = сначала f, потом g — композиция создаёт новую функцию.
  • Оба оператора — обычные функции стандартной библиотеки, без магии.
Проверьте себя
1. Чему равно x |> f?
Af(f x)
Bf x
Cx f
DОшибке
2. Что делает f >> g?
AПрименяет сначала g, потом f
BСоздаёт функцию: сначала f, потом g
CСкладывает результаты f и g
DСравнивает f и g
3. В чём разница между |> и >>?
AНет разницы
B|> работает со значениями, >> — с функциями
C>> только для чисел
D|> создаёт новую функцию без аргумента