Skip to content
Oeiuwq Faith Blog OpenSource Porfolio

sini/gen-select

Selector algebra for attributed graph positions

sini/gen-select.json
{
"createdAt": "2026-05-26T17:28:22Z",
"defaultBranch": "main",
"description": "Selector algebra for attributed graph positions",
"fullName": "sini/gen-select",
"homepage": null,
"language": "Nix",
"name": "gen-select",
"pushedAt": "2026-05-30T23:42:23Z",
"stargazersCount": 2,
"topics": [],
"updatedAt": "2026-06-02T17:50:11Z",
"url": "https://github.com/sini/gen-select"
}

gen-select — selector algebra for attributed graph positions

Section titled “gen-select — selector algebra for attributed graph positions”

CI License: MIT Sponsor

Pure pattern matching library for Nix. Selectors are { __sel = tag; ... } attrsets matched by matches against an ID-based accessor context. Depends on gen-algebra pure tier only.

  • [Overview]!(#overview)
  • [Gen Ecosystem]!(#gen-ecosystem)
  • [Quick Start]!(#quick-start)
  • [Core API]!(#core-api)
  • [Adapters]!(#adapters)
  • [Demo Templates]!(#demo-templates)
  • [Performance]!(#performance)
  • [Testing]!(#testing)
  • [Theoretical Foundations]!(#theoretical-foundations)

gen-select provides a compositional selector algebra for querying positions in attributed graphs. Selectors express structural and data predicates — “nodes whose parent has attribute X”, “nodes with a descendant matching Y” — without coupling to any particular graph representation.

The library has three layers:

  1. Constructors — build selector values (sel.star, sel.attrs, sel.and, sel.within, etc.)
  2. Match enginematches selector id ctx evaluates a selector against an accessor-based context
  3. Adapters — bridge selectors to gen-scope and gen-graph

Selectors are plain attrsets tagged with __sel. No special types, no evaluation order dependencies, no side effects.

LibraryRole
gen-algebraPure primitives (search, record, identity)
gen-schemaTyped registries (kinds, instances, collections, refs)
gen-aspectsAspect types (traits, classification, dispatch)
gen-graphGraph queries (combinators, traversals, fixpoint)
gen-scopeScope graphs (construction, evaluation, resolution)
gen-selectSelector algebra (pattern matching over graph positions)
gen-bindModule binding (inject args into NixOS modules)
gen-deriveRule dispatch (stratified phases, fixpoint, conflict resolution)
{
inputs.gen-select.url = "github:sini/gen-select";
outputs = { gen-select, nixpkgs, ... }:
let
lib = nixpkgs.lib;
sel = gen-select.lib;
in {
# sel.matches, sel.star, sel.attrs, sel.and, ...
};
}
let
lib = (import <nixpkgs> {}).lib;
sel = import ./path/to/gen-select { inherit lib; };
in
sel.matches (sel.attrs { role = "backend"; }) "api" myContext
# => true if myContext.data "api" has role = "backend"

matches takes a context record with five accessor functions:

FieldTypePurpose
dataid -> attrsetattribute data for a node
parentid -> id | nullimmediate parent
childrenid -> [id]direct children
ancestorsid -> [id]ancestor chain (parent to root)
siblingsid -> [id]sibling nodes (same parent, excluding self)

The id is not stored in the context — it is the second argument to matches.

matches : selector -> id -> context -> bool

Evaluates a selector against the node identified by id in the given context. Dispatches on the __sel tag.

sel.matches (sel.attrs { type = "service"; }) "web" ctx
# => true if (ctx.data "web").type == "service"
ConstructorSignatureMatches when
sel.star-> selectoralways
sel.attrs aattrset -> selectorall k:v in a equal in data id; missing key = no match
sel.and ss[selector] -> selectorall match; sel.and [] = true
sel.any ss[selector] -> selectorany matches; sel.any [] = false
sel.not sselector -> selectordoes not match
sel.has sselector -> selectorany child matches
sel.within sselector -> selectorany ancestor matches
sel.parentMatches sselector -> selectorimmediate parent matches
sel.child p csel -> sel -> selectorsugar: and [ c (parentMatches p) ]
sel.descendant a dsel -> sel -> selectorsugar: and [ d (within a) ]
sel.when fnfn -> selectorfn id ctx returns true

The __sel tags are: "star", "attrs", "and", "any", "not", "has", "within", "parentMatches", "child", "descendant", "when".

Note: child and descendant are sugar — they expand to and compositions at construction time and carry no distinct __sel tag at runtime.

sel.when wraps a bare lambda as a selector. By default, two when selectors cannot be compared for equality (lambdas are not comparable in Nix).

For equality support, pass an intensional function (created via genPure.mkIntensional):

myPred = genPure.mkIntensional {
name = "is-backend";
closure = { };
__functor = _: id: ctx: (ctx.data id).role == "backend";
};
sel.when myPred
isIdentified : selector -> bool
selectorEq : selector -> selector -> bool

isIdentified returns true when a when selector wraps an intensional function (has name, __functor, and closure fields).

selectorEq compares two selectors. For when selectors, it delegates to genPure.intensionalEq when both are intensional; otherwise returns false. For all other selector types, it uses structural equality (==).

adapters.scope.mkContext : { node, get } -> context

Builds a selector context from gen-scope’s accessor pair. Maps scope accessors to the five context fields:

Context fieldImplementation
dataid: (node id).decls
parentid: (node id).parent
childrenid: attrNames (get id "children")
ancestorswalks parent chain, cycle-safe
siblingschildren of parent, excluding self
adapters.graph.mkPredicate : selector -> context -> (id -> bool)
adapters.graph.mkSelectPredicate : selector -> context -> (attrset -> bool)

mkPredicate curries matches into a predicate suitable for gen-graph traversal filters (e.g., reachableWhere).

mkSelectPredicate wraps matches for use with graph.select, expecting an attrset with an id field.

Maps CSS selector syntax concepts to gen-select combinators. Demonstrates sel.attrs as element/class selectors, sel.descendant and sel.child as CSS combinators, sel.has as :has(), and sel.not as :not(). Tests verify the mapping against a DOM-like tree context.

Maps SQL WHERE clause concepts to gen-select. Demonstrates sel.attrs as column equality, sel.and/sel.any as AND/OR, sel.not as NOT, and sel.when for range predicates and LIKE patterns. Tests verify against a table-like flat context.

gen-select evaluates selectors lazily through accessor functions. When wired to gen-scope:

  • O(1) data access — each ctx.data id call hits gen-scope’s memoized evaluation; repeated access for the same node evaluates once
  • Proportional to selector structurematches only inspects what the selector asks for; sel.attrs { role = "x"; } touches one field, not the full node
  • No Tier 2 materialization — selectors never enumerate all nodes; the caller decides iteration scope
  • Structural combinators short-circuitsel.and stops at the first false; sel.any stops at the first true
  • Ancestor/child walks are boundedwithin and has traverse only the relevant subtree or chain, not the full graph

Memory consumption is proportional to what the selector inspects, not the total graph size.

Terminal window
# CI test suite (core library)
cd ci && just ci
# CSS selectors demo
cd examples/css-selectors && just ci
# SQL WHERE demo
cd examples/sql-where && just ci

Or via nix-unit directly:

Terminal window
nix-unit --override-input gen-select . --flake ./ci

gen-select draws on both academic research and industrial standards. Each source falls into one of two categories: Implements (the library directly realizes constructs from the source) or Informed by (the source shaped design decisions without direct structural correspondence).

SourceRelationship
Palmer, Filardo & Wu (2024)Intensional Functionssel.when wraps lambdas as selectors; isIdentified and selectorEq realize intensional identity and equality via program point (name) comparison ONLY — a further conservative approximation (Palmer 2024 Theorem 1 (closure consistency) / §2.3 conservative-equality model)
CSS Selectors Level 4 — W3CStructural selector vocabulary: sel.has as :has(), sel.not as :not(), sel.child and sel.descendant as CSS combinators
SourceRelationship
Neron, Tolmach, Visser & Wachsmuth (2015)A Theory of Name ResolutionThe five-field accessor context (data, parent, children, ancestors, siblings) models the P-edge (parent/child/ancestor) traversal axes of a scope graph; does NOT implement the resolution calculus (no well-formedness, specificity, shadowing, or import edges)
Arntzenius & Krishnaswami (2016)Datafun: A Functional DatalogMonotone pattern matching over lattice-structured data informed the design of composable selector predicates that respect structural ordering
Reynolds (1983)Types, Abstraction, and Parametric PolymorphismParametricity constraints on selector generality: selectors operate uniformly over any context satisfying the accessor interface, not over concrete representations
Mokhov (2017)Algebraic Graphs with ClassAlgebraic composition of graph predicates (overlay/connect as selector combinators) informed how sel.and/sel.any compose without coupling to graph representation
XPath 3.1 — W3CAxis-based navigation model (ancestor, child, descendant, sibling) informed the context accessor vocabulary and structural combinator naming