pattern
Safe HaskellSafe-Inferred
LanguageHaskell2010

Pattern.Graph

Description

Graph Lens: Interpretive view of Pattern structures as graphs.

This module provides the Graph Lens feature, which enables interpreting Pattern structures as graph structures (nodes, relationships, walks) through a minimal, elegant design based on a single predicate.

Overview

A Graph Lens provides an interpretive view of a Pattern as a graph structure. Rather than defining graph concepts (nodes, relationships, walks) as intrinsic properties of Pattern, they emerge through the lens's interpretation. This design enables multiple graph views of the same Pattern and supports higher-order graphs where relationships or entire graphs become nodes.

Core Design

The Graph Lens consists of:

  • scopePattern: The Pattern that defines the boundary for all graph operations. Only direct elements of this pattern are considered for graph structure.
  • testNode: A predicate determining which direct elements are nodes. All other graph concepts (relationships, walks) derive from this single predicate.

Design Principles

  1. **Scope-bounded operations**: All graph operations only consider direct elements of scopePattern, never descending into nested structures.
  1. **Single predicate foundation**: Only testNode is required. All other graph predicates (relationships, walks, etc.) are derived from this.
  2. **Context captured at construction**: If a predicate needs context, that context must be captured when the predicate is created, not during evaluation.
  3. **Interpretation, not intrinsic**: Graph structure is not a property of Pattern itself, but an interpretation through the lens.

Categorical Interpretation

Graph Lens provides a functorial interpretation where Pattern structures are transformed into graph interpretations. The transformation Pattern → Graph interpretation is functorial in nature.

GraphView

GraphView is the universal transformation interface produced from a GraphLens (via toGraphView) or from a PatternGraph (via toGraphView). It pairs a snapshot GraphQuery with a categorized, traversable list of graph elements, enabling lazy pipeline transformations:

pipeline :: PatternGraph Subject -> PatternGraph Subject
pipeline graph =
  materialize canonicalClassifier LastWriteWins
  . mapWithContext canonicalClassifier enrich
  . filterGraph canonicalClassifier isRelevant dissolve
  . mapAllGraph updateTimestamp
  . Pattern.PatternGraph.toGraphView canonicalClassifier
  $ graph

Finalize a GraphView pipeline back to a PatternGraph by calling materialize.

Example

>>> let graphPattern = pattern "graph" [point "a", point "b", pattern "r1" [point "a", point "b"]]
>>> let isAtomic (Pattern _ els) = null els
>>> let atomicLens = GraphLens graphPattern isAtomic
>>> nodes atomicLens
[Pattern "a" [],Pattern "b" []]

See design/graph-lens.md and specs023-graph-lensquickstart.md for comprehensive examples and usage patterns.

Synopsis

Graph Lens Type

data GraphLens v Source #

A Graph Lens provides an interpretive view of a Pattern as a graph structure.

The lens consists of: * scopePattern: The Pattern that defines the boundary for all graph operations. Only direct elements of this pattern are considered for graph structure. * testNode: A predicate determining which direct elements are nodes. All other graph concepts (relationships, walks) derive from this predicate.

Categorical Interpretation

Graph Lens provides a functorial interpretation where Pattern structures are transformed into graph interpretations. The transformation Pattern → Graph interpretation is functorial in nature.

Design Principles

  1. Scope-bounded: All operations only consider direct elements of scopePattern
  2. Single predicate foundation: Only testNode is required, all else derives
  3. Context at construction: Predicate context captured when lens is created
  4. Interpretation, not intrinsic: Graph structure is an interpretation, not a property of Pattern itself

Example

>>> let atomicLens = GraphLens pattern (\(Pattern _ els) -> null els)
>>> nodes atomicLens
[[a], [b], [c]]

Constructors

GraphLens 

Fields

mkGraphLens :: Pattern v -> (Pattern v -> Bool) -> GraphLens v Source #

Construct a GraphLens using a predicate to identify nodes.

This encapsulates the context for interpreting graph structure.

Node Operations

nodes :: GraphLens v -> [Pattern v] Source #

Extract all nodes from the graph lens.

Nodes are direct elements of scopePattern that satisfy the testNode predicate.

Time Complexity

O(n) where n is the number of direct elements in scopePattern

Example

>>> let lens = GraphLens pattern isAtomic
>>> nodes lens
[[a], [b], [c]]

isNode :: GraphLens v -> Pattern v -> Bool Source #

Determine if a Pattern is a node according to the lens.

This is the context-aware version that uses the lens's testNode predicate. The lens parameter provides the predicate context.

Example

>>> let lens = GraphLens pattern isAtomic
>>> isNode lens (point "a")
True
>>> isNode lens (pattern "rel" [point "a", point "b"])
False

Relationship Operations

isRelationship :: GraphLens v -> Pattern v -> Bool Source #

Determine if a Pattern is a relationship according to the lens.

A relationship is a non-node pattern with exactly two node elements.

Properties

  • Must not be a node (does not satisfy testNode predicate)
  • Must have exactly two elements
  • Both elements must be nodes (according to the lens)

Example

>>> let lens = GraphLens pattern isAtomic
>>> let rel = pattern "knows" [point "Alice", point "Bob"]
>>> isRelationship lens rel
True

relationships :: GraphLens v -> [Pattern v] Source #

Extract all relationships from the graph lens.

Relationships are non-node patterns with exactly two node elements.

Time Complexity

O(n) where n is the number of direct elements in scopePattern

Example

>>> let lens = GraphLens pattern isAtomic
>>> relationships lens
[[knows | [Alice], [Bob]], [likes | [Bob], [Charlie]]]

source :: GraphLens v -> Pattern v -> Maybe (Pattern v) Source #

Extract the source node from a relationship.

For directed relationships, the source is the first element. Returns Nothing if the pattern is not a relationship.

Example

>>> let lens = GraphLens pattern isAtomic
>>> let rel = pattern "knows" [point "Alice", point "Bob"]
>>> source lens rel
Just (point "Alice")

target :: GraphLens v -> Pattern v -> Maybe (Pattern v) Source #

Extract the target node from a relationship.

For directed relationships, the target is the second element. Returns Nothing if the pattern is not a relationship.

Example

>>> let lens = GraphLens pattern isAtomic
>>> let rel = pattern "knows" [point "Alice", point "Bob"]
>>> target lens rel
Just (point "Bob")

reverseRel :: Pattern v -> Pattern v Source #

Reverse the direction of a relationship pattern.

Swaps the first and second elements, effectively reversing the relationship direction.

Example

>>> let rel = pattern "knows" [point "Alice", point "Bob"]
>>> reverseRel rel
pattern "knows" [point "Bob", point "Alice"]

Walk Operations

isWalk :: GraphValue v => GraphLens v -> Pattern v -> Bool Source #

Determine if a Pattern is a walk according to the lens.

A walk is a non-node pattern whose elements are all relationships, where consecutive relationships share nodes (target of one equals source of next).

Properties

  • Must not be a node (does not satisfy testNode predicate)
  • All elements must be relationships (according to the lens)
  • Consecutive relationships must be connected

Example

>>> let lens = GraphLens pattern isAtomic
>>> let walk = pattern "path" [rel1, rel2, rel3]
>>> isWalk lens walk
True

walks :: GraphValue v => GraphLens v -> [Pattern v] Source #

Extract all walks from the graph lens.

Walks are non-node patterns whose elements are all relationships, where consecutive relationships share nodes.

Time Complexity

O(n) where n is the number of direct elements in scopePattern

Example

>>> let lens = GraphLens pattern isAtomic
>>> walks lens
[[path | [rel1], [rel2], [rel3]]]

walkNodes :: GraphValue v => GraphLens v -> Pattern v -> [Pattern v] Source #

Extract nodes from a walk in traversal order.

Returns the source of the first relationship, followed by the targets of subsequent relationships. Returns empty list if the pattern is not a valid walk.

Example

>>> let lens = GraphLens pattern isAtomic
>>> let walk = pattern "path" [rel1, rel2]
>>> walkNodes lens walk
[pattern "A", pattern "B", pattern "C"]

Navigation Operations

neighbors :: GraphValue v => GraphLens v -> Pattern v -> [Pattern v] Source #

Find all neighbors of a node.

Neighbors are nodes connected to the given node via relationships (either as source or target).

Time Complexity

O(r) where r is the number of relationships in the graph

Example

>>> let lens = GraphLens pattern isAtomic
>>> neighbors lens (point "Alice")
[point "Bob", pattern "Charlie"]

incidentRels :: GraphValue v => GraphLens v -> Pattern v -> [Pattern v] Source #

Find all relationships involving a node.

Returns relationships where the node is either source or target.

Time Complexity

O(r) where r is the number of relationships in the graph

Example

>>> let lens = GraphLens pattern isAtomic
>>> incidentRels lens (point "Alice")
[[knows | [Alice], [Bob]], [likes | [Charlie], [Alice]]]

degree :: GraphValue v => GraphLens v -> Pattern v -> Int Source #

Compute the degree of a node (number of incident relationships).

The degree is the count of relationships where the node is either source or target.

Time Complexity

O(r) where r is the number of relationships in the graph

Example

>>> let lens = GraphLens pattern isAtomic
>>> degree lens (point "Alice")
3

GraphQuery constructor

fromGraphLens :: (GraphValue v, Eq v) => GraphLens v -> GraphQuery v Source #

Construct a GraphQuery from a GraphLens.

All fields are derived from existing Graph functions. queryNodeById and queryRelationshipById perform O(n) / O(r) scans (no index available from GraphLens). queryContainers scans relationships and walks.

Note: defined here (not in Pattern.Graph.GraphQuery) to avoid a circular import between GraphQuery and Graph.

GraphView constructor

toGraphView :: (GraphValue v, Eq v) => GraphClassifier extra v -> GraphLens v -> GraphView extra v Source #

Construct a GraphView from a GraphLens and a GraphClassifier.

The classifier assigns a GraphClass tag to each element in the lens scope. viewQuery is the snapshot query built from the same lens.

Note: defined here (not in Pattern.Graph.GraphQuery) to avoid a circular import — GraphQuery cannot import Pattern.Graph.

GraphView and Substitution (re-exported from Pattern.Graph.Types)

data GraphView extra v Source #

A universal interface bridging graph representations to a single queryable and transformable state.

GraphView pairs a GraphQuery (for snapshot traversal) with an explicitly categorized, traversable list of graph elements. It acts as a lazy transformation target: transformations operate over viewElements while algorithms use viewQuery for context lookups.

Construct via toGraphView (from a PatternGraph) or toGraphView (from a GraphLens). Finalize via materialize.

Design Principles

  • viewQuery is the unmodified snapshot — it never reflects in-flight transformations, ensuring deterministic context-aware operations.
  • viewElements is the mutable list — transformations update this list.
  • The pairing is shallow: no deep nesting, just a list and a query record.

Constructors

GraphView 

Fields

data Substitution v Source #

Defines how to handle gaps in container structures (e.g., walks) when an internal element is removed by filterGraph.

When a walk's internal relationship is filtered out, the walk becomes structurally invalid. Substitution dictates the repair strategy.

Design Rationale

There is no one-size-fits-all fallback for container gaps. Sometimes a gap means the entire container is invalid; other times a surrogate placeholder should be sutured in. An explicit parameter forces callers to reason about filtering consequences directly.

Constructors

DeleteContainer

Remove the entire container (walk, annotation) when any of its required elements are absent after filtering.

SpliceGap

Remove the missing element and re-stitch the remaining elements into a shorter container. For a walk, this collapses the gap.

ReplaceWithSurrogate (Pattern v)

Substitute the missing element with a fixed surrogate pattern. The surrogate is inserted in place of the removed element.