Packages

PipeAssign provides a macro for capturing intermediate values in Elixir pipe chains without breaking the flow or requiring separate assignment statements.

Current section

Files

Jump to
pipe_assign lib pipe_assign.ex
Raw

lib/pipe_assign.ex

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2025 Dmitriy Pertsev
defmodule PipeAssign do
@moduledoc """
PipeAssign provides a macro for capturing intermediate values in Elixir pipe chains
without breaking the flow or requiring separate assignment statements.
## Problem and Solution
Traditional Elixir code forces you to choose between clean pipes and intermediate access:
# Clean pipes, but no intermediate access
final_result = data |> transform() |> process() |> finalize()
# Intermediate access, but broken flow
step1 = data |> transform()
step2 = step1 |> process()
final_result = step2 |> finalize()
PipeAssign solves this with `assign_to/2`:
import PipeAssign
data
|> transform()
|> assign_to(step1) # Capture without breaking flow
|> process()
|> assign_to(step2)
|> finalize()
|> assign_to(result) # Assign to result variable
See the [README](https://hexdocs.pm/pipe_assign) for installation
instructions, examples, benchmarking results, and detailed usage guidance.
"""
alias PipeAssign.ErrorHandler
@doc """
This macro provides in-place assignments for pipes.
The variable will be assigned the piped value and the value continues through the pipe,
allowing you to capture intermediate results without breaking the pipe flow or requiring
separate assignment statements.
## Examples
Import the module and use `assign_to/2` in your pipes:
iex> import PipeAssign
iex> [1, 2, 3]
...> |> Enum.map(&(&1 * 2))
...> |> Enum.sum()
...> |> assign_to(result)
12
iex> result
Chain multiple assignments in a single pipe:
iex> import PipeAssign
iex> %{name: "John", age: 30}
...> |> Map.put(:email, "john@example.com")
...> |> assign_to(with_email)
...> |> Map.put(:active, true)
...> |> assign_to(complete)
...> |> Map.keys()
...> |> length()
...> |> assign_to(result)
4
iex> result
4
iex> Map.get(with_email, :email)
"john@example.com"
iex> Map.get(complete, :active)
true
Works seamlessly with existing variables (no compiler warnings):
iex> import PipeAssign
iex> temp = nil
iex> "hello world"
...> |> String.upcase()
...> |> assign_to(temp)
...> |> String.length()
11
iex> temp
"HELLO WORLD"
Use without import for occasional assignments:
iex> [1, 2, 3, 4, 5]
...> |> Enum.filter(&rem(&1, 2) == 0)
...> |> PipeAssign.assign_to(evens)
[2, 4]
iex> evens
[2, 4]
"""
defmacro assign_to(value, var) do
var_name = ErrorHandler.validate_and_extract_var_name(var)
caller = __CALLER__
var_exists = Enum.any?(Macro.Env.vars(caller), fn {name, _ctx} -> name == var_name end)
if var_exists do
quote do
value = unquote(value)
_ = unquote(var)
unquote(var) = value
value
end
else
quote do
value = unquote(value)
var!(unquote(var)) = value
value
end
end
end
end