Documentation
¶
Overview ¶
Package graph is a typed dependency graph. A Graph describes Node values inertly, each a name and a constructor; Graph.Build constructs what its roots reach and returns it as a System of Dependency values in computed layers. The package knows nothing of lifecycles: a consumer that runs a System, starting its layers in order and stopping them in reverse, reads each node's part from its value, as the lifecycle package's Coordinator does.
The package exports:
- Graph, the description of the nodes, and New, which returns an empty one
- Graph.Define, which adds a node with its constructor and runs nothing
- Graph.Replace, which swaps a node's constructor before Build, for a test's substitute
- Graph.Observe, which adds a function a Build calls with each node's name as it begins to construct it, for tracing what a Build reaches
- Graph.Build, which constructs what the roots reach into a System
- Node, a typed handle on one node, and Node.Name, its name
- Ref, any Node whatever its type, sealed to Node, and its Name
- Scope, what a constructor receives
- Scope.Use, which returns a dependency's value, building it if needed
- Scope.After, which orders the node after another without its value
- System, what one Build constructed
- System.Get, which returns a node's value
- System.Layers, which returns the built nodes in layers
- Dependency, one built node: its name and value
Discovery ¶
A constructor declares its dependencies by using them: Scope.Use builds the dependency if this Build has not, and records the edge, so the edges are discovered as the constructors run, depth-first from the roots, and can depend on what the constructors see. Each node is built at most once per Build, so a node shared by several dependents is constructed once and they all receive its value. A node that is neither a root nor reached by a Use is never constructed, so a Build of a subset of the roots brings up only that subset's dependencies. Each Build is independent and constructs fresh values.
Scope.After adds an order-only edge: it passes no value and builds nothing, and holds only when its target is in the System by some other path.
Layers ¶
The layers are the longest-path layering of the discovered Use and After edges, as System.Layers states: every node's dependencies sit in lower layers, so the nodes of one layer can start together once the layers below them have.
Errors and panics ¶
A constructor's error fails the Build, labelled with its node's name and wrapping the error; a failing dependency aborts every constructor whose Use reached it, and Build reports the dependency's error.
Scope.Use returns the dependency's value itself, so the abort unwinds by panic, which Build recovers. A constructor must not recover it: one that returns, with a value or an error, after a Use it made aborted would lose the failure, so Build panics naming the constructor and the Use.
Wiring mistakes panic with a "graph: " message, as each symbol's documentation states: a dependency cycle, a node used on a Graph it was not defined on, a Scope used after its constructor returned, a recovered abort, a Define or Replace after Build, a Get of a node not in the System, a nil constructor or observer, and an empty or duplicate name. Build checks its roots, for a nil node or one defined on another Graph, before it constructs any of them, so a mistake in any root panics before a constructor runs.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Dependency ¶
Dependency is one built node: its name and its constructed value. A lifecycle reads what the node takes part in from the value's methods.
type Graph ¶
type Graph struct {
// contains filtered or unexported fields
}
Graph describes a set of nodes and their constructors. Describing is inert: Graph.Define and Graph.Replace record constructors and run none, and only Graph.Build constructs anything. The first Build freezes the description: Define and Replace panic after it. A Graph is not safe for concurrent use.
func (*Graph) Build ¶
Build constructs every node the roots reach through Scope.Use, each at most once, depth-first, and returns them as a System in computed layers. Each Build is independent and constructs fresh values. A constructor's error fails the Build, labelled with its node's name and wrapping the error, whether the node is a root or a dependency some Use reached. Build panics on a nil root, a root defined on another Graph, or a dependency cycle, among the nodes Use reaches or the ordering edges After adds among them. It checks every root before it constructs any, so a wiring mistake in one root panics before another root's constructor runs. It also panics when a constructor returns after a Use it made aborted, which only a recover in the constructor allows: a constructor must not recover. The first Build freezes g: Graph.Define and Graph.Replace panic after it.
func (*Graph) Define ¶
Define adds a node named name, constructed by ctor, and returns its handle. It runs nothing: ctor runs only when a Graph.Build reaches the node. Define panics once g has run a Build, when name is empty or already defined on g, or when ctor is nil.
func (*Graph) Observe ¶
Observe adds fn, which every later Build calls with a node's name as it begins to construct the node, before the constructor runs: each node a Build reaches, once, in the order it reaches them depth-first, the one whose constructor fails included. It is for tracing what a Build constructs, such as a test asserting which nodes a run brought up; fn sees only the name and cannot change the Build. Observe panics on a nil fn.
type Node ¶
type Node[T any] struct { // contains filtered or unexported fields }
Node is one node of a Graph, defined by Graph.Define: a name and a constructor of a T. It is a handle, not a value; a constructor reaches the value through Scope.Use, and a program through System.Get.
type Ref ¶
type Ref interface {
// Name returns the name the node was defined with.
Name() string
// contains filtered or unexported methods
}
Ref is any Node, whatever its type: what Graph.Build takes as roots and Scope.After as an ordering target, and what names a node without its type. It is sealed; only *Node[T] implements it.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope is what a constructor receives: the means to reach its dependencies and to order it after other nodes. It is valid only while the constructor runs; every method panics once the constructor has returned.
func (*Scope) After ¶
After orders the node under construction after r: it starts after r and stops before it. It passes no value and never builds r; the edge holds only when r is in the System, as a root or a node some Use reached, and is dropped otherwise. After panics when r is nil or was defined on another Graph, or after the constructor has returned.
func (*Scope) Use ¶
Use returns n's value, building n first when this Build has not, and records that the node under construction depends on n. When n's constructor fails, or a dependency's does as n is built, Use does not return: it aborts the calling constructor by panicking, and Build recovers the panic and reports the failing node's error. A constructor must not recover that panic: when one returns after a Use it made aborted, Build panics naming it. Use panics when n was defined on another Graph, when n is under construction (a cycle, named by its path), after a Use in the same constructor aborted, or after the constructor has returned.
type System ¶
type System struct {
// contains filtered or unexported fields
}
System is what one Graph.Build constructed: every node the roots reached, with its value, in computed layers.
func (*System) Get ¶
Get returns n's value in s. It panics when n is not in s: a node the roots did not reach, or one defined on another Graph.
func (*System) Layers ¶
func (s *System) Layers() [][]Dependency
Layers returns the System's nodes in layers, layer 0 first: a node with no dependencies is in layer 0, and any other node is one layer above the highest of its Use and After dependencies in the System, so every node's dependencies sit in layers below it. Within a layer, nodes are in definition order. Each call returns a fresh copy.