|
| 1 | +# RFC-002: Pattern as Container and Graph Substrate |
| 2 | + |
| 3 | +**Status:** accepted |
| 4 | +**Date:** 2025-11-01 |
| 5 | +**Authors:** @akollegger |
| 6 | +**Repository:** [github.com/relateby/pattern-hs](https://github.com/relateby/pattern-hs) |
| 7 | +**Supersedes:** [`proposals/design/DESIGN.md`](DESIGN.md), [`proposals/design/pattern-category.md`](pattern-category.md) |
| 8 | +**Related modules:** `Pattern.Core`, `Pattern.Graph.GraphLens`, `Pattern.PatternGraph` |
| 9 | + |
| 10 | +## Summary |
| 11 | + |
| 12 | +`Pattern v` is a recursive decorated-sequence container: a value paired with an ordered |
| 13 | +list of element Patterns. It is not itself a graph; graph structure is an *interpretation* |
| 14 | +imposed by external machinery. The same `Pattern v` can be interpreted as a directed graph, |
| 15 | +an undirected graph, a walk, or any other graph-like structure depending on which view is |
| 16 | +applied. This separation of container from interpretation is the load-bearing design choice |
| 17 | +that enables flexibility across graph semantics without committing to any single one. |
| 18 | + |
| 19 | +## Motivation |
| 20 | + |
| 21 | +Graph data structures traditionally encode semantic commitments in their types: a |
| 22 | +`DiGraph` is directed, an `UndirectedGraph` is not, a `WeightedGraph` carries edge |
| 23 | +weights. This forces users to choose an interpretation at construction time and makes |
| 24 | +multiple simultaneous interpretations cumbersome. |
| 25 | + |
| 26 | +`Pattern v` inverts this: a universal container accumulates structure, and views impose |
| 27 | +interpretations on demand. The same Pattern that represents a person's professional |
| 28 | +network can simultaneously represent their social graph, their org chart, or a sequence |
| 29 | +of career transitions — without duplicating data. The interpretation that is salient |
| 30 | +depends on the question being asked, not on how the data was originally stored. |
| 31 | + |
| 32 | +This is the motivation for separating substrate from interpretation. |
| 33 | + |
| 34 | +## Design |
| 35 | + |
| 36 | +### The Pattern Data Structure |
| 37 | + |
| 38 | +```haskell |
| 39 | +data Pattern v = Pattern |
| 40 | + { value :: v -- decoration: what kind of pattern this is |
| 41 | + , elements :: [Pattern v] -- the pattern's content, as an ordered sequence |
| 42 | + } |
| 43 | +``` |
| 44 | + |
| 45 | +Patterns are recursive: every element of a Pattern is itself a Pattern. The `elements` |
| 46 | +field is the sequence that defines the pattern's content; the `value` field is a |
| 47 | +decoration that describes what the sequence means. |
| 48 | + |
| 49 | +**Key insight**: The elements field IS the pattern — it contains the sequence that defines |
| 50 | +the content. The value field decorates that content. For example, a pattern `[chord | C, E, G]` |
| 51 | +has value `chord` decorating the sequence `[C, E, G]`. |
| 52 | + |
| 53 | +### Conceptual Model: Decorated Sequences |
| 54 | + |
| 55 | +Conceptually, patterns are **decorated sequences**: |
| 56 | +- `elements` holds the sequence that defines the pattern |
| 57 | +- `value` provides decoration about what kind of pattern it is |
| 58 | +- Order of elements is semantically significant — patterns are ordered |
| 59 | +- Recursive structure enables nested sequences (patterns containing patterns) |
| 60 | + |
| 61 | +The tree structure is an implementation detail that supports sequences in memory. There |
| 62 | +is no contradiction between these views — the tree structure is how sequences are |
| 63 | +represented, and tree traversal preserves sequence order. |
| 64 | + |
| 65 | +### Structural Classifications |
| 66 | + |
| 67 | +Patterns have structural classifications based on element count. These describe what |
| 68 | +patterns *are* structurally, independent of any interpretation. |
| 69 | + |
| 70 | +**Atomic Pattern** — `elements == []`. The fundamental building block; the leaf of any |
| 71 | +Pattern tree. Nodes in the graph interpretation are atomic patterns. |
| 72 | + |
| 73 | +**Singular Pattern** — exactly one element. A pattern wrapping a single sub-pattern. |
| 74 | + |
| 75 | +**Pattern with Elements** — one or more elements. The general case. |
| 76 | + |
| 77 | +**Nested Pattern** — elements that themselves have elements, enabling arbitrary depth. |
| 78 | + |
| 79 | +### Graph Interpretations |
| 80 | + |
| 81 | +Patterns are *interpreted* as graph elements through views. The same structural |
| 82 | +classification rules apply across all interpretations. |
| 83 | + |
| 84 | +#### Nodes |
| 85 | + |
| 86 | +A node is an atomic pattern — a Pattern with no elements. |
| 87 | + |
| 88 | +```haskell |
| 89 | +node :: v -> Pattern v |
| 90 | +node v = Pattern v [] |
| 91 | +``` |
| 92 | + |
| 93 | +Notation equivalences: |
| 94 | +``` |
| 95 | +Cypher: (n:Person) |
| 96 | +Pattern: [n:Person] |
| 97 | +``` |
| 98 | + |
| 99 | +#### Relationships |
| 100 | + |
| 101 | +**Undirected**: a pattern with exactly two atomic elements; order is semantically irrelevant. |
| 102 | +**Directed**: a pattern with exactly two atomic elements, where element order encodes direction — |
| 103 | +`element[0]` is the source, `element[1]` is the target. |
| 104 | + |
| 105 | +```haskell |
| 106 | +-- Source/target accessors |
| 107 | +source :: Pattern v -> Pattern v |
| 108 | +source (Pattern _ (s:_)) = s |
| 109 | + |
| 110 | +target :: Pattern v -> Pattern v |
| 111 | +target (Pattern _ [_, t]) = t |
| 112 | + |
| 113 | +-- Reverse direction |
| 114 | +reverseRel :: Pattern v -> Pattern v |
| 115 | +reverseRel (Pattern v [a, b]) = Pattern v [b, a] |
| 116 | +``` |
| 117 | + |
| 118 | +Notation equivalences: |
| 119 | +``` |
| 120 | +Cypher: (a)-[r:KNOWS]->(b) Pattern: [r:KNOWS | a, b] -- directed, a→b |
| 121 | +Cypher: (a)<-[r:KNOWS]-(b) Pattern: [r:KNOWS | b, a] -- directed, b→a |
| 122 | +Cypher: (a)-[r:KNOWS]-(b) Pattern: [r:KNOWS | a, b] -- undirected (order irrelevant) |
| 123 | +``` |
| 124 | + |
| 125 | +**Design rationale**: direction is encoded positionally in the existing ordered-sequence |
| 126 | +structure, with no extra type machinery. Reversing a relationship is a structural |
| 127 | +operation: swap the two elements. Directed vs. undirected is a semantic distinction |
| 128 | +managed by the consumer, not a structural one. |
| 129 | + |
| 130 | +#### Walks |
| 131 | + |
| 132 | +A walk is an ordered sequence of relationships allowing continuous traversal: entering |
| 133 | +each relationship at one endpoint and exiting at the other, which becomes the entry |
| 134 | +point of the next relationship. |
| 135 | + |
| 136 | +```haskell |
| 137 | +walk :: v -> [Pattern v] -> Pattern v |
| 138 | +walk meta relationships = Pattern meta relationships |
| 139 | +``` |
| 140 | + |
| 141 | +``` |
| 142 | +Cypher: (a)-[r1]->(b)<-[r2]-(c)-[r3]->(d) |
| 143 | +Pattern: [walk | [r1 | a, b], [r2 | c, b], [r3 | c, d]] |
| 144 | +``` |
| 145 | + |
| 146 | +Valid walk compositions in Pattern notation: |
| 147 | +``` |
| 148 | +[w | [r1 | a, b]] =~ (a)-[r1]->(b) |
| 149 | +[w | [r1 | a, b], [r2 | c, b]] =~ (a)-[r1]->(b)<-[r2]-(c) |
| 150 | +[w | [r1 | a, b], [r2 | b, a]] =~ (a)-[r1]->(b)-[r2]->(a) |
| 151 | +``` |
| 152 | + |
| 153 | +#### Summary |
| 154 | + |
| 155 | +| Concept | Pattern Structure | Convention | |
| 156 | +|----------------|------------------------------|-------------------------------------| |
| 157 | +| Node | `[v]` | Atomic pattern (no elements) | |
| 158 | +| Undirected Rel | `[r \| (a), (b)]` | Order semantically irrelevant | |
| 159 | +| Directed Rel | `[r \| (a), (b)]` | element[0]=source, element[1]=target | |
| 160 | +| Walk | `[meta \| rel1, rel2, ...]` | Consecutive rels share endpoints | |
| 161 | + |
| 162 | +### Typeclass Instances |
| 163 | + |
| 164 | +`Pattern v` is implemented with the following typeclass instances (all accepted): |
| 165 | + |
| 166 | +- `Functor` — structure-preserving map over values: `fmap f` transforms every `v` while |
| 167 | + preserving the Pattern tree structure |
| 168 | +- `Foldable` — fold over all values in traversal order |
| 169 | +- `Traversable` — effectful traversal, combining Functor and Foldable |
| 170 | +- `Applicative` — zip-like application of a Pattern of functions to a Pattern of values |
| 171 | +- `Comonad` — `extract` retrieves the root value; `extend` maps with local context; |
| 172 | + `duplicate` produces the Pattern of all sub-Patterns |
| 173 | +- `Eq`, `Ord`, `Hashable` — structural equality, ordering, and hashing |
| 174 | +- `Semigroup`, `Monoid` — Pattern combination with identity |
| 175 | +- `Show`, `ToJSON`, `FromJSON` — display and serialization |
| 176 | + |
| 177 | +### Category-Theoretic Perspective |
| 178 | + |
| 179 | +`Pattern v` admits a categorical interpretation through a `CategoryLens`, which defines |
| 180 | +what counts as objects and how values compose: |
| 181 | + |
| 182 | +```haskell |
| 183 | +data CategoryLens v = CategoryLens |
| 184 | + { valueOp :: v -> v -> v -- value composition (magma operation) |
| 185 | + , isObject :: Pattern v -> Bool -- which patterns are objects |
| 186 | + } |
| 187 | +``` |
| 188 | + |
| 189 | +Under a graph lens, leaf patterns (atoms) are objects and arity-2 patterns are morphisms. |
| 190 | +Objects and morphisms are identified *post-hoc* through predicates, not enforced at |
| 191 | +construction time. |
| 192 | + |
| 193 | +**Composition** preserves structure rather than flattening: |
| 194 | + |
| 195 | +```haskell |
| 196 | +compose :: (v -> v -> v) -> Pattern v -> Pattern v -> Pattern v |
| 197 | +compose f (Pattern v1 elems1) (Pattern v2 elems2) = |
| 198 | + Pattern (f v1 v2) [Pattern v1 elems1, Pattern v2 elems2] |
| 199 | +``` |
| 200 | + |
| 201 | +The original patterns become elements of the composite, maintaining internal structure. |
| 202 | + |
| 203 | +**Morphism equivalence** — two patterns are equivalent as morphisms when they have the |
| 204 | +same leaf sequence (same objects in sequence order). Three levels: |
| 205 | + |
| 206 | +- *Structural equivalence*: same complete leaf sequence |
| 207 | +- *Endpoint equivalence*: same source and target objects (hom-set membership) |
| 208 | +- *Custom*: domain-specific equivalence relation |
| 209 | + |
| 210 | +**The category is not intrinsic to `Pattern`**; it emerges from the chosen lens. The |
| 211 | +same `Pattern v` can support many different categorical structures by varying the lens, |
| 212 | +validity rules, or equivalence relation. This is the "schema-lazy" property: maximum |
| 213 | +flexibility during construction, multiple valid interpretations on demand. |
| 214 | + |
| 215 | +### Forgetful Functor Hierarchy |
| 216 | + |
| 217 | +Views form a hierarchy of forgetful functors, each losing information as it descends: |
| 218 | + |
| 219 | +``` |
| 220 | +Pat[Full] --F₁--> Pat[Shape] --F₂--> Pat[Topology] --F₃--> Pat[Connected] |
| 221 | +``` |
| 222 | + |
| 223 | +```haskell |
| 224 | +forgetValues :: Pattern v -> Pattern () |
| 225 | +forgetValues = fmap (const ()) |
| 226 | +``` |
| 227 | + |
| 228 | +This enables analogical reasoning: match patterns that are structurally similar under |
| 229 | +some forgetting, even if their values differ. |
| 230 | + |
| 231 | +### Key Properties |
| 232 | + |
| 233 | +1. **Schema-lazy** — Patterns do not commit to specific graph semantics; interpretation |
| 234 | + happens in the view. The same Pattern is valid under any compatible view. |
| 235 | +2. **Compositional** — Views can be composed, stacked, or swapped without changing |
| 236 | + underlying patterns. |
| 237 | +3. **Open-ended** — New views can be defined for any graph-like interpretation. |
| 238 | +4. **Categorical** — Each view defines a functor; forgetful matching uses functor |
| 239 | + composition. |
| 240 | + |
| 241 | +## Open Questions |
| 242 | + |
| 243 | +1. **Navigation functions** — `source`, `target`, `nodes`, `relationships` as explicit |
| 244 | + Pattern operations are planned but not implemented. They would provide ergonomic |
| 245 | + access to graph-structured patterns without requiring a full view. |
| 246 | + |
| 247 | +2. **Zipper for focus** — A `Zipper v` data structure supporting `parents` / |
| 248 | + `ancestors` operations would enable DOM-style upward navigation and focus-based |
| 249 | + editing. Currently deferred; comonadic `extend` and `duplicate` partially address |
| 250 | + the use cases. |
| 251 | + |
| 252 | +3. **Pattern morphisms** — Explicit `PatternMorphism v w` types with `homomorphism` |
| 253 | + and `forget` combinators are planned as future work for categorical reasoning. |
| 254 | + |
| 255 | +4. **Standard view instances** — Named instances like `DirectedView` and `UndirectedView` |
| 256 | + are planned to reduce boilerplate for the common cases. |
| 257 | + |
| 258 | +## Alternatives |
| 259 | + |
| 260 | +**Typed graph representations** — encoding node/relationship/walk distinction in the |
| 261 | +type system (e.g. GADTs indexed by sort) was rejected. `Pattern v`'s uniform-value |
| 262 | +architecture is load-bearing for typeclass instances, and sort indexing would require |
| 263 | +either a parallel type or a substantial rewrite. The runtime cost of kind predicates |
| 264 | +is acceptable. |
| 265 | + |
| 266 | +**Forcing interpretation at construction** — building a `Graph` type with committed |
| 267 | +semantics at the constructor site was rejected in favor of the substrate posture. |
| 268 | +Post-hoc interpretation via views is more flexible without being more complex. |
0 commit comments