Type expression syntax

TL;DR

  • Pipelang is the type language Pipelogic uses to describe data on every wire — small, structural, recursive.
  • A type is built from atomic keywords (Int32, Double, String, Bool, …), composed via composite forms — [t] (list), (a, b) (tuple), {x: A, y: B} (record), A | B (union), Name<T> (named-type application).
  • Lowercase identifiers (t, a, frame) are type variables; uppercase identifiers are concrete names from the catalog.
  • The same grammar runs in the platform compiler, the C++ runtime, and the Python catalog — type expressions written in component.yml are checked identically wherever they appear.
  • The page you are reading is the mental model. For the full grammar, every production, every disambiguation rule, every worked corner case, fetch the reference from the CLI: ppl docs get type-api/type-syntax.

What a Pipelang type actually buys you

Pipelang is the only place where Pipelogic enforces a contract before runtime. A component declares input_type: "Image"; another component declares output_type: "Image"; the platform connects them only if the types unify. There is no "almost compatible", no JSON dict that happens to look right at the call site, no runtime KeyError on a missing field — the wire either type-checks or refuses to exist.

The grammar is intentionally small so that this check is fast, understandable, and consistent across language boundaries. Same expression, same meaning, whether the producer is Python and the consumer is C++ or the other way around.

The forms you will use most often

FormExampleWhen you reach for it
AtomicInt32, Double, StringWire carries a scalar.
NamedImage, AudioFrame, TensorWire carries a domain object from the catalog.
List[BoundingBox]Homogeneous sequence of the element type.
Tuple(Image, String)Heterogeneous fixed-arity sequence; order matters.
Record{x: Double, y: Double}Named fields; an ad-hoc struct on the wire.
UnionImage | DepthImageEither alternative is acceptable to the consumer.
Generic applicationPolygon<Double>, Maybe<a>A catalog type parameterized by one or more type arguments.
Type variablea, t, elemA placeholder the platform binds at composition time (lowercase identifier).
RefinementInt32<0..=255>, String<email>A base type narrowed by a predicate — a range, enum, pattern, or format alias.

The grammar combines these recursively: [Maybe<{x: Double, y: Double}>] is a list of optional points, every form composed from the table above.

Generic vs concrete: a one-line rule

If the identifier starts lowercase, it is a generic placeholder the platform binds when the component is wired into a backend. If it starts uppercase (or is one of the ten atomic keywords), it is concrete. That single rule covers most of what you will write day to day; everything beyond it — pack variables $ts, pattern expansions T..., field-pack expansions $ns: T..., refinements that narrow a base by a predicate (Int32<0..=255>), and the bounded oneof[…] set — is for advanced cases where the type is variadic or constrained.

Where this fits

The type expression language is the contract between components. It is the input to the type checker, the source of the connectivity graph in the visual builder, the basis for autocomplete in the agent catalog. Everything visible across components — what fits where, what does not — comes from this grammar. Treat the type signature as the spec; the platform treats it that way too.

For the full grammar

The website covers the model and the common forms. For the full grammar with every production, every disambiguation rule ((T) vs (T,), T | U vs T |), pack variables, pattern expansions, refinements, the oneof[…] bounded type, default-value literals, equality and canonicalization rules, and worked examples for every catalog edge case, fetch the reference from the CLI:

ppl docs get type-api/type-syntax

Related

  • /type-api/types — the type system as a platform concept.
  • /component-api/component-contract — the file that wraps these expressions.
  • /type-api/catalog — catalog of built-in named types.
  • /concepts/components — how a typed component fits into a backend.

Was this page helpful?