Что делает оператор |> (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, 2, 3, 4] |> Enum.map(&(&1 * 2)) → [2, 4, 6, 8]
-
[2, 4, 6, 8] |> Enum.filter(&(&1 > 4)) → [6, 8]
-
[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-кода, делающий язык очень выразительным и удобным для построения чистых, модульных и читаемых цепочек вычислений.