Named types

TL;DR

  • A named type is a registered pipelang type — Image, BoundingBox, AudioFrame, Tensor, and other entries in the platform's type catalog — that components reference from their input_type and output_type declarations.
  • Named types are the platform's shared vocabulary for domain-shaped data. The same Image resolves to the same definition for every image-handling component; the same BoundingBox resolves to the same definition for every detector. The named type sits between the language primitives and each component's contract.
  • The type system resolves a named type when two vertices are connected: matching name and matching structural shape lets the edge land; anything else is rejected at the connect call, before the operation is recorded.
  • The named-type catalog is read-only to workspace users. Browse it with ppl type list / get / print / tree; reference the names from component.yml. The catalog itself is curated by the platform.
  • Named types compose with the rest of the type language. A list of bounding boxes is [BoundingBox]; an optional image is Maybe<Image>; a record carrying both is {img: Image, boxes: [BoundingBox]}. The structural constructors live in the language; the named types live in the catalog; both work together in any declaration.

Why named types exist as a separate concept

A named type is a single catalog entry that gives one canonical name to a recurring domain shape, so that every component referencing that name resolves to the same definition. It exists alongside the language's atomic types (Int64, Double, String, Bool) and composite constructors (lists, tuples, records, unions), which can already express any shape structurally.

Structural expressibility and shared identity are different things. An image is structurally a record of atomic types plus a Bytes payload — its pixel dimensions, its pixel format, and the encoded data. If each image-handling component declared its own copy of that record, two structurally-identical records would carry no signal that they describe the same kind of value, and the type system would treat name matching as unavailable and fall back to structural comparison alone. A named type supplies the shared identity that structural comparison cannot.

Image is the platform's canonical name for a typed image value: its width and height, its pixel format, and the encoded data, packaged as one named type that every image-handling component references. When a vertex declares input_type: Image, the type system resolves it to that definition, and any vertex declaring output_type: Image can be wired into it. The shared name is what lets the two ends compose without restating their structure.

The same applies to AudioFrame (a data tensor plus a sample_rate), Tensor<t> (a shape plus the element data, parametrised by element type), BoundingBox (a detected class plus a rectangle), Landmark (a point plus a confidence), and the rest of the catalog. Each is a contract between component authors: a component that emits a given name produces that name's shape with that name's meaning. The catalog holds those contracts.

Mental model

  component author                   backend author
  ┌──────────────────────────────┐   ┌────────────────────────┐
  │ component.yml:               │   │ ppl backend connect    │
  │   input_type:  Image         │◀─▶│   --from-vertex 1 …    │
  │   output_type: [BoundingBox] │   │   --to-vertex   2 …    │
  └──────────────────────────────┘   └────────────────────────┘
                 │                                │
                 │ resolves via                   │
                 ▼                                ▼
  ┌───────────────────────────────────────────────────────────┐
  │ named-types catalog (per remote)                          │
  │   Image       := { ... }                                  │
  │   BoundingBox  := { ... }                                 │
  │   AudioFrame   := { ... }                                 │
  └───────────────────────────────────────────────────────────┘

The component author writes a name in the manifest; the backend author wires two vertices; the type system resolves both ends against the catalog and validates the connection. The catalog is what gives the names meaning across components — it resolves each name to a single definition rather than leaving the type system to compare structures alone.

See Types for how named types compose with atomic and composite types, and Components for where the names appear in component manifests.

How named types compose with the rest of the type language

Use this framing whenever you wonder "can I wrap this named type in a list / tuple / record".

Yes. Named types are first-class values in the type language; anywhere a type can appear, a named type can appear — [BoundingBox] is a list of detections, just as [Int64] is a list of integers. The composition rules are the same whether the inner type is atomic, composite, or named, and wrapping a named type does not lose any of its semantic guarantees. Types covers how the three categories compose and how the type checker walks the composed structure when it validates an edge.

How the catalog is curated

Use this framing the first time you wonder "can I add my own named type".

The named-type catalog is read-only from a workspace user's perspective. The verbs available to workspace users are list, get, print, and tree — all of them queries. There is no create path on the user surface; the catalog is curated by the platform, and entries are added through a separate process.

That constraint follows from named types being shared contracts. Adding Image to the catalog commits every future image-handling component to that shape; changing it later would break every component that referenced the earlier definition. Curating the catalog centrally, rather than letting users define their own entries, is what keeps each definition consistent across the components that reference it.

For a shape that does not yet have a named type, use a composite type (a record, a tuple, a list of atomic types) until the shape stabilises. Structural types are inline in the manifest, do not require catalog entries, and the type system handles them the same way as named types — they carry no shared name across components. When a shape recurs widely enough to warrant a name, propose its addition to the catalog through the process the platform exposes for that.

Browsing the catalog

Use this when you need to know what names exist or what one of them is structurally.

The query verbs cover the four shapes of "tell me about this catalog":

ppl type list                          # everything
ppl type list --query Frame            # substring filter
ppl type get AudioFrame                # canonical definition
ppl type get AudioFrame --recursive    # plus every transitive dependency
ppl type print AudioFrame              # pipelang source suitable for piping into a .type file
ppl type tree AudioFrame               # structural AST showing how the type is built
ppl type tree AudioFrame --depth 2     # cap the expansion depth

The right verb depends on the question. list is for discovery; get is for the canonical definition; print is for piping the definition into a file; tree is for understanding the structural composition (especially useful when two vertices fail to connect and the user needs to see exactly where the shapes diverge).

The catalog is per-remote: switching between remotes (production, staging, on-premise) may resolve the same name to different definitions. That is intentional; remotes have independent type registries because the platform's curated set evolves over time and per environment.

When the connect call rejects a named type

Use this framing the first time ppl backend connect refuses an edge with a "type mismatch" error.

The most common shapes of named-type connect failures:

  • Different names — one vertex emits AudioFrame, the other expects Tensor. The names do not match, so the edge is refused. The fix is usually a transformation between them (or, if no transformation exists, a converter component).
  • Declaration drift — both vertices reference Image, but the two component releases declare it with different structural wrappers or arguments. The fix is to align the two ends, usually by bumping one of the components to a release whose declaration matches.
  • Wrapping mismatch — one side emits Image, the other expects [Image]. Same named type, different structural wrapper. The fix is a transformation component that bridges the cardinality.
  • Local shadow — a .type file in the working directory's component config is shadowing the remote definition with a different shape. The fix is to resolve from a clean directory, or to update the local .type to match the catalog.

ppl type tree <name> on both ends is the right diagnostic for understanding why the structures disagree. It expands the type to its leaves, so the divergence point is visible.

Where this fits

Named types are the platform's shared vocabulary for domain-shaped data. They sit between the language primitives (shared structurally but carrying no domain meaning) and the components' typed contracts (which carry meaning but need shared names to compose). The catalog holds the definitions; central curation keeps them consistent; keeping the catalog read-only to users prevents one-off definitions from proliferating. Because the names are real types, the type checker enforces them strictly — Image connects to Image, never to a structural lookalike, across every wire in the graph.

The result is that whether one component's image is the same kind of value as another's is resolved by the type checker, from the shared name, rather than left to the user to confirm.

Related

  • Types — atomic, composite, and named types together.
  • Components — where input_type / output_type reference named types.
  • Backend operationsconnect is the verb that resolves named types between two vertices.
  • Type syntax reference — the full pipelang grammar named types compose with.
  • Solutions — how the shared vocabulary supports cross-product composition.

Was this page helpful?