Skip to content

The identity cluster: just, an identity concept, and cross-kind and_then in the functor #350

Description

@Bronek

Three carriers never short-circuit — their failure grade is the bottom of the grade semilattice: choice<Ts...> (no error channel at all), expected<T, copack<>> (uninhabited error, legal since #345), and the proposed fn::just<T> — the canonical, minimal identity carrier, the family's unit as a value (precedent: std::execution::just, the immediately-ready computation; not Haskell's Maybe-Just — worth one disambiguating line in docs). They are canonically, payload-preservingly isomorphic; none is privileged. "Identity" is therefore a concept, not a type.

Binding across the cluster is lossless precisely because the channels a carrier switch drops are uninhabited — the grade union with the empty set forgets nothing. Outside the cluster the same switch would drop a live error channel, and remains rejected.

Proposal:

  1. fn::just<T> — minimal surface (value_type, value(), transform, and_then, ==), no error-side members. Mandate not some_copack<T>, message directing to choice: dispatch granularity belongs to the engine, uniform per payload, so just<copack<Ts...>> would behave exactly as choice<Ts...> — the mandate keeps one spelling per meaning (just = identity over a non-copack atom; choice = identity over a copack; expected<T, copack<>> = the two-channel carrier in its identity state). just<choice<...>> stays legal — a choice is an atom everywhere. It also makes just structurally single-branch. Leaning: support just<void> — with deduction, just{} is a much nicer spelling of "nothing special here, but it's a success" than expected<void, copack<>>{} (and matches std::execution::just() sending zero values); to be confirmed in the PR.
  2. The identity concept — detail-level: some_choice<T> || some_just<T> || (some_expected<T> && empty_copack<error_type>); public spelling to bikeshed here (some_identity?).
  3. The and_then functor gains a cluster arm: when the input fits the concept, the callback may return any identity-kind carrier. Result rule, result-kind-driven and host-independent: per-branch results all choice → the grade-spliced superset choice (Collapse differing choice results of and_then into the superset choice #349's join engine); otherwise all branches must converge to one identity-kind type, and the result is that type (the bind follows the function); mixed carriers reject. The arm is engine-direct — it dispatches join-mode over value() (for expected<copack<...>, copack<>> that is a copack, the very same engine call choice::and_then makes over its own payload). No carrier header learns another carrier: the verb layer already names them all and is the licensed cross-carrier place.
  4. Members stay strict same-kind. Member and_then is the carrier's own bind — T A → (A → T B) → T B — and relaxing it would change the type's bind semantics; the cluster bind is Kleisli composition along the canonical isomorphisms, a composition-layer operation that belongs to the functor. The member's static_assert message should point to the functor form.

or_else has no cluster counterpart: choice and just have no error surface, and expected<T, copack<>>'s is vacuous per #345 — the callback is never instantiated, so there is no return type to follow; vacuous verbs keep the input's kind.

The family's laws hold up to the canonical isomorphisms (e.g. x | and_then([](auto&& v){ return just(f(v)); })x | transform(f), with carriers differing under follow-the-function) — documented and tested modulo the iso. The gate condition is deliberately on the input's channels only, leaving a future extension open without redesign: an identity input could lawfully bind into any monadic kind (the bridge from infallible into fallible pipelines); out of scope here.

Tests: the cluster matrix through the functor (each carrier × each return kind), member strictness negatives, expected<copack<A,B>, copack<>> with all-choice branches evaporating into the superset choice, the just mandate as a compile-time probe, noexcept and constexpr twins. Docs: the new type needs its stanzas from day one (coordinate with #121).

Depends on #349.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestrelease-0.1Planned for release 0.1

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions