Конвейер |> и композиция функций
Учимся писать читаемые цепочки преобразований данных с помощью оператора конвейера |>.
Оператор |> (pipe) берёт значение слева и подставляет его последним аргументом функции справа:
x |> fравноf x.
Когда над данными выполняется несколько преобразований подряд, вложенные вызовы становятся нечитаемыми: List.length (List.filter pred (List.map f xs)) приходится читать справа налево. Оператор |> переворачивает запись так, чтобы она читалась в естественном порядке — как конвейер.
Как работает |>
Определение оператора предельно простое:
let ( |> ) x f = f x
То есть x |> f — это просто f x. Благодаря левой ассоциативности и каррированию цепочка читается сверху вниз:
let result =
[1; 2; 3; 4; 5; 6]
|> List.filter (fun x -> x mod 2 = 0) (* [2;4;6] *)
|> List.map (fun x -> x * x) (* [4;16;36] *)
|> List.fold_left (+) 0 (* 56 *)
Почему работает с каррированием
Заметьте, что List.filter pred — частично применённая функция, ждущая список. Конвейер list |> List.filter pred подаёт ей этот список. Именно каррирование делает |> удобным: каждый шаг — функция, у которой не хватает последнего аргумента.
Обратный конвейер @@
Есть зеркальный оператор @@ (apply): f @@ x равно f x, но он правоассоциативен и низкоприоритетен, помогая убрать скобки в правой части:
print_int @@ 2 + 3 (* вместо print_int (2 + 3) *)
Композиция функций
Иногда нужно собрать новую функцию из нескольких, не вызывая сразу. Оператор композиции легко определить:
let ( << ) f g = fun x -> f (g x) (* (f << g) x = f (g x) *)
let inc x = x + 1
let double x = x * 2
let f = inc << double (* сначала double, потом inc *)
(* f 5 = inc (double 5) = inc 10 = 11 *)
Разница тонкая, но важная: |> сразу применяет функции к конкретному значению, а композиция << создаёт новую функцию для применения позже.
Как работает под капотом
Поскольку |> — обычная функция, компилятор её инлайнит: после оптимизации x |> f |> g превращается в те же инструкции, что и g (f x). Накладных расходов нет — чистый выигрыш в читаемости. Приоритет операторов в OCaml определяется их первым символом, поэтому |> левоассоциативен и связывает слабее, чем применение функции, — благодаря чему вся конструкция парсится как цепочка.
Частые ошибки
- Ждать, что значение подставится первым аргументом.
|>подставляет его последним; функции вродеList.mapспециально устроены так, что список — последний аргумент. - Путать
|>и@@. Они дают одно, но направление и ассоциативность разные. - Считать композицию
<<встроенной. Её берут из библиотеки (Base) или определяют сами.
Итоги
x |> fравноf xи позволяет читать цепочки преобразований сверху вниз.- Конвейер опирается на каррирование: каждый шаг — функция без последнего аргумента.
- Композиция собирает новую функцию, конвейер сразу применяет функции к значению.