View Source Handoff.DAG (Handoff v0.1.0)
Provides functionality for building and validating directed acyclic graphs (DAGs) of functions.
This module is the core of the Handoff library, allowing you to:
- Create empty computation graphs
- Add functions to the graph with their dependencies
- Validate the graph for correctness before execution
dag-structure
DAG Structure
A DAG in Handoff is represented as a map with:
:functions
- A map of function IDs toHandoff.Function
structs
examples
Examples
# Create a new DAG
dag = Handoff.DAG.new()
# Define functions
# Assumes MyPipelineFunctions module exists, e.g.:
# defmodule MyPipelineFunctions do
# def transform_data(data_list), do: Enum.map(data_list, &(&1 * 2))
# end
source = %Handoff.Function{
id: :data_source,
args: [],
code: &Elixir.Function.identity/1,
extra_args: [[1, 2, 3, 4, 5]]
}
transform = %Handoff.Function{
id: :transform,
args: [:data_source],
code: &MyPipelineFunctions.transform_data/1
}
aggregation = %Handoff.Function{
id: :aggregate,
args: [:transform],
code: &Enum.sum/1
}
# Build the DAG
dag =
dag
|> Handoff.DAG.add_function(source)
|> Handoff.DAG.add_function(transform)
|> Handoff.DAG.add_function(aggregation)
# Validate the DAG
case Handoff.DAG.validate(dag) do
:ok ->
# DAG is valid and ready for execution
IO.puts("DAG is valid")
{:error, {:missing_dependencies, missing}} ->
IO.puts("DAG has missing dependencies: #{inspect(missing)}")
{:error, {:cycle_detected, cycle}} ->
IO.puts("DAG contains a cycle at: #{inspect(cycle)}")
end
validation
Validation
The validate/1
function performs two critical checks:
- It ensures all dependencies reference existing functions
- It detects cycles in the graph using depth-first search
A valid DAG is required before execution.
Link to this section Summary
Functions
Adds a function to the DAG.
Creates a new empty DAG with a specified ID or generates a new one.
Validates that the DAG has no cycles and all dependencies exist.
Link to this section Functions
Adds a function to the DAG.
parameters
Parameters
- dag: The current DAG structure
- function: A Handoff.Function struct to add to the DAG
returns
Returns
- Updated DAG with the function added
example
Example
dag =
Handoff.DAG.new()
|> Handoff.DAG.add_function(%Handoff.Function{
id: :source,
args: [],
code: &:rand.uniform/1,
extra_args: [100]
})
Creates a new empty DAG with a specified ID or generates a new one.
parameters
Parameters
- id: (Optional) The ID to assign to the DAG. If nil, a
make_ref/0
will be used.
example
Example
dag_with_specific_id = Handoff.DAG.new("some-specific-id")
dag_with_generated_id = Handoff.DAG.new()
Validates that the DAG has no cycles and all dependencies exist.
returns
Returns
:ok
if the DAG is valid{:error, {:missing_function, id}}
if references to an undefined function was found{:error, {:cycle_detected, id}}
if a cycle is found in the graph
example
Example
iex> dag = Handoff.DAG.new()
iex> dag = Handoff.DAG.add_function(dag, %Handoff.Function{id: :a, args: [], code: &Elixir.Function.identity/1, extra_args: [1]})
iex> dag = Handoff.DAG.add_function(dag, %Handoff.Function{id: :b, args: [:a], code: &Map.get/2, extra_args: [:a]})
iex> Handoff.DAG.validate(dag)
:ok
error-cases
Error cases
iex> dag = Handoff.DAG.new()
iex> dag = Handoff.DAG.add_function(dag, %Handoff.Function{id: :a, args: [:b], code: &Elixir.Function.identity/1})
iex> Handoff.DAG.validate(dag)
{:error, {:missing_function, :b}}
iex> dag = Handoff.DAG.new()
iex> dag = Handoff.DAG.add_function(dag, %Handoff.Function{id: :a, args: [:b], code: &Map.get/2, extra_args: [:b]})
iex> dag = Handoff.DAG.add_function(dag, %Handoff.Function{id: :b, args: [:a], code: &Map.get/2, extra_args: [:a]})
iex> {:error, {:cyclic_dependency, cycle}} = Handoff.DAG.validate(dag)
iex> Enum.sort(cycle)
[:a, :b]