Agent Skills: Tuple Nav Composition

Tuple and product structure navigation with composition

UncategorizedID: plurigrid/asi/tuple-nav-composition

Install this agent skill to your local

pnpm dlx add-skill https://github.com/plurigrid/asi/tree/HEAD/ies/music-topos/.codex/skills/tuple-nav-composition

Skill Files

Browse the full folder contents for tuple-nav-composition.

Download Skill

Loading file tree…

ies/music-topos/.codex/skills/tuple-nav-composition/SKILL.md

Skill Metadata

Name
tuple-nav-composition
Description
Tuple and product structure navigation with composition

Tuple Nav Composition

Core Concept

Navigation through product structures (tuples, named tuples, records) where multiple fields must be accessed and composed in parallel.

A TupleNav is a bundled Navigator that operates on all fields of a product simultaneously, maintaining structural integrity through composition.

Why Tuple Navigation?

Standard Specter navigators work on sequences (ALL, each element). But what about simultaneous access to multiple fields?

# Without TupleNav: Access fields separately, type mismatch risk
person = (name="Alice", age=30, email="alice@example.com")

name_nav = @late_nav([INDEX(1)])     # But tuples aren't indexed this way!
age_nav = @late_nav([INDEX(2)])      # Fragile, error-prone

# With TupleNav: Access structurally, type-safe composition
record_nav = @tuple_nav([:name, :age])  # Access both fields as unit

Architecture

TupleNav Structure

struct TupleNav
    fields::Vector{Symbol}           # Field names to navigate
    navigators::Dict{Symbol, Navigator}  # Navigator for each field
    composition_strategy::Symbol      # :parallel, :sequential, :reduce
    output_type::Type                # (Field1Type, Field2Type, ...)
end

Product Types

| Type | Navigation | |------|-----------| | (T1, T2, T3) | Positional tuple | | NamedTuple{(:a, :b, :c)} | Named access | | @NamedTuple{a::T1, b::T2} | Struct-like | | Dict{K, V} | Key-based dictionary | | Record/DataClass | Field-based |

Composition Strategies

:parallel - Access all fields simultaneously

nav = @tuple_nav([:x, :y, :z], strategy=:parallel)
result = nav_select(nav, (x=1, y=2, z=3), id)
# => ((x=1), (y=2), (z=3))  # All accessed at once

:sequential - Access fields in order, threading results

nav = @tuple_nav([:x, :y, :z], strategy=:sequential)
# Field y receives result from x, z receives from y, etc.

:reduce - Fold/reduce across fields

nav = @tuple_nav([:x, :y, :z], strategy=:reduce)
# Combine results across fields: x + y + z

API

TupleNav Creation

@tuple_nav(fields, strategy=:parallel) Creates a TupleNav for navigating product types.

# Access name and email from person record
nav = @tuple_nav([:name, :email])

person = (name="Alice", email="alice@example.com", age=30)
result = nav_select(nav, person, identity)
# => (name="Alice", email="alice@example.com")

tuple_nav(fields::Vector{Symbol}, structure_type::Type) Explicitly typed TupleNav.

PersonType = NamedTuple{(:name, :age, :email)}

nav = tuple_nav([:name, :email], PersonType)
# Type-checked at compile time

Composition

compose_tuple_navs(nav1::TupleNav, nav2::TupleNav) :: TupleNav Combines two product navigators.

nav_identity = @tuple_nav([:id, :name])     # Extract identity
nav_contact = @tuple_nav([:email, :phone])  # Extract contact

nav_combined = compose_tuple_navs(nav_identity, nav_contact)
# Result has all 4 fields: id, name, email, phone

Nested Products

nested_tuple_nav(path::Vector, fields::Vector{Symbol}) Navigate to a product, then select fields.

# Navigate to users list, then select name/email from each
nav = nested_tuple_nav([keypath("users"), ALL], [:name, :email])

data = Dict(
  "users" => [
    (name="Alice", age=30, email="alice@example.com"),
    (name="Bob", age=25, email="bob@example.com")
  ]
)

result = nav_select(nav, data, identity)
# => [
#      (name="Alice", email="alice@example.com"),
#      (name="Bob", email="bob@example.com")
#    ]

Field Projection

project_tuple_nav(nav::TupleNav, fields::Vector{Symbol}) Extract subset of fields from a TupleNav.

nav_full = @tuple_nav([:id, :name, :age, :email])
nav_subset = project_tuple_nav(nav_full, [:name, :email])
# New navigator accesses only name and email

Structural Composition

Key insight: TupleNav maintains type safety across composition.

# Type 1: Person
Person = NamedTuple{(:name, :age)}

# Type 2: Contact
Contact = NamedTuple{(:email, :phone)}

# Type 3: User (combines both)
User = NamedTuple{(:name, :age, :email, :phone)}

# Composition is type-safe
nav_person = @tuple_nav([:name, :age], Person)
nav_contact = @tuple_nav([:email, :phone], Contact)
nav_user = compose_tuple_navs(nav_person, nav_contact)
# => TupleNav for User type ✓

Parallel Semantics

With :parallel strategy, all fields are accessed in parallel (logically):

nav = @tuple_nav([:x, :y, :z], strategy=:parallel)

# Execution order doesn't matter—all fields see the same input
result1 = nav_select(nav, structure, f)
result2 = nav_select(nav, structure, f)
# => Always identical (deterministic parallel access)

Under the hood, this uses color-aware SplitMix64 seeding to ensure parallel reads remain deterministic (see color-envelope-preserving skill).

Dictionary Projection

TupleNav also works on dictionaries with structured keys:

nav = @tuple_nav([:user_id, :user_name, :user_email])

data = Dict(
  :user_id => 42,
  :user_name => "Alice",
  :user_email => "alice@example.com",
  :user_age => 30
)

result = nav_select(nav, data, identity)
# => Dict(:user_id => 42, :user_name => "Alice", :user_email => "alice@example.com")

Integration with Type Inference

TupleNav output types are automatically inferred by the type system:

nav = @tuple_nav([:x, :y, :z])
# Type: (T1, T2, T3) → (T1', T2', T3')
# where T_i' is the refined type of field i

sig = navigator_signature(nav)
# => TypeSignature(
#     input: NamedTuple{(:x,:y,:z)},
#     output: NamedTuple{(:x,:y,:z)},  # same structure
#     preserves: field types
#   )

Composition with Constraints

TupleNav can apply navigators to each field:

# Each field goes through its own Navigator
field_navs = Dict(
  :x => @late_nav([pred(ispositive)]),
  :y => @late_nav([pred(ispositive)]),
  :z => @late_nav([pred(ispositive)])
)

nav = @tuple_nav([:x, :y, :z], field_navigators=field_navs)
# Each field must pass its predicate

Error Handling

Missing field:

nav = @tuple_nav([:name, :email, :phone])
person = (name="Alice", email="alice@example.com")  # missing :phone

nav_select(nav, person, id)
# => FieldMissingError("Field :phone not found in tuple")

Type mismatch:

nav = @tuple_nav([:age])  # Expects numeric field
person = (name="Alice", age="thirty")  # age is string

nav_select(nav, person, id)
# => TypeError("Expected Int, got String for field :age")

Performance

| Operation | Complexity | Notes | |-----------|-----------|-------| | Create TupleNav | O(n) | n = number of fields | | Parallel access | O(n) | All fields accessed once | | Sequential access | O(n²) | Each step threads to next | | Reduce | O(n) | Single fold over fields | | Caching | O(1) | Cache key includes field list |

Related Skills

  • type-inference-validation - Validates field types at compile time
  • constraint-generalization - Combines field constraints across products
  • specter-navigator-gadget - Base Navigator system that TupleNav builds on
  • möbius-path-filtering - Ensures product navigation is topologically valid

Use Cases

Example 1: Extract contact info from user records

nav = @tuple_nav([:name, :email, :phone])
users = [
  (id=1, name="Alice", email="alice@ex.com", phone="555-1234"),
  (id=2, name="Bob", email="bob@ex.com", phone="555-5678")
]

contacts = map(u -> nav_select(nav, u, identity), users)
# All users' contact triples extracted

Example 2: Parallel aggregation

nav = @tuple_nav([:revenue, :costs, :profit], strategy=:parallel)

quarterly_data = (
  revenue=100_000,
  costs=60_000,
  profit=40_000
)

result = nav_select(nav, quarterly_data, x -> x * 1.1)  # 10% increase
# => (revenue=110_000, costs=66_000, profit=44_000)

Example 3: Nested product navigation

nav = nested_tuple_nav(
  [keypath("employees"), ALL],
  [:name, :salary]
)

company = Dict(
  "employees" => [
    (name="Alice", salary=80_000, dept="Engineering"),
    (name="Bob", salary=75_000, dept="Sales")
  ]
)

result = nav_select(nav, company, identity)
# => Extract name/salary for all employees

References

  • Product types: "Types and Programming Languages" - Pierce
  • Cartesian logic: "Basic Intuitionistic Linear Logic" - Girard
  • Parallel semantics: "Parallel Haskell" - Peyton Jones et al.