Что делает оператор |> (pipe)?

В Elixir оператор |> (pipe operator) используется для последовательной передачи результата одной функции в качестве первого аргумента другой функции. Он позволяет писать цепочки вызовов в читаемом, линейном виде, что делает код более декларативным и легким для понимания.

Общий синтаксис

value
|> function1()
|> function2()
|> function3()

Это то же самое, что:

function3(function2(function1(value)))

Как это работает

Когда мы пишем:

list |> Enum.map(&(&1 \* 2))

Elixir интерпретирует это как:

Enum.map(list, &(&1 \* 2))

То есть значение до оператора |> подставляется в качестве первого аргумента в функцию после него.

Примеры использования

Пример 1: Работа с коллекциями

\[1, 2, 3, 4\]
|> Enum.map(&(&1 \* 2))
|> Enum.filter(&(&1 > 4))
|> Enum.sum()

Пошагово:

  1. [1, 2, 3, 4] |> Enum.map(&(&1 * 2)) → [2, 4, 6, 8]

  2. [2, 4, 6, 8] |> Enum.filter(&(&1 > 4)) → [6, 8]

  3. [6, 8] |> Enum.sum() → 14

Пример 2: Обработка строк

" hello world "
|> String.trim()
|> String.upcase()
|> String.replace("WORLD", "ELIXIR")

Результат: "HELLO ELIXIR"

Пример 3: Передача в функцию с несколькими аргументами

Если нужно передать значение не как первый аргумент, то |> не подойдёт напрямую. Например:

String.replace("hello world", "world", "elixir")

Если сделать так:

"hello world"
|> String.replace("world", "elixir") # корректно

Это работает, потому что "hello world" становится первым аргументом, а "world" и "elixir" — вторым и третьим.

Комбинирование с анонимными функциями

Иногда удобно комбинировать pipe с анонимными функциями:

data
|> (fn x -> x \* 2 end).()
|> IO.inspect()

Хотя такой стиль не распространён — обычно используют именованные функции или сокращённую запись через &.

Pipe и модульные функции

Оператор |> особенно эффективен при работе с библиотеками типа Enum, Stream, String, File, Path, Map, Keyword, List, Date, и другими модулями, где почти все функции принимают первым аргументом основной объект.

Пример с Map:

%{a: 1, b: 2}
|> Map.put(:c, 3)
|> Map.delete(:a)
|> Map.keys()

Особенности и ограничения

Pipe подставляет значение только в ПЕРВЫЙ аргумент. Если вам нужно подставить его внутрь, используйте анонимную функцию:

value
|> (fn x -> function(a, x, c) end).()

Pipe может использоваться только с функциями. Нельзя пропускать вызов скобок.

Неверно:

data |> IO.inspect

Лучше:

<br/>data |> IO.inspect()

Pipe нельзя использовать с макросами, которые не поддерживают подстановку первого аргумента, или в ситуациях, где компилятор не сможет правильно интерпретировать синтаксис.

Пример с собственными функциями

defmodule Math do
def double(x), do: x \* 2
def square(x), do: x \* x
end
2
|> Math.double()
|> Math.square() # => 16

Аналогично: Math.square(Math.double(2))

Почему это удобно

  • Повышает читаемость: операции читаются сверху вниз.

  • Убирает вложенные скобки.

  • Поддерживает чистый декларативный стиль (особенно в функциональном программировании).

  • Идеально сочетается с pipelines в Enum, Stream, Ecto, Phoenix.

Где часто встречается

  • В обработке коллекций (Enum, Stream)

  • В веб-фреймворках (Plug, Phoenix)

  • В написании кастомных пайплайнов (flow-like архитектура)

  • В написании DSL (domain-specific languages)

Оператор |> — фундаментальный элемент идиоматического Elixir-кода, делающий язык очень выразительным и удобным для построения чистых, модульных и читаемых цепочек вычислений.