You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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.
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?).
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.
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).
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 proposedfn::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:
fn::just<T>— minimal surface (value_type,value(),transform,and_then,==), no error-side members. Mandatenot some_copack<T>, message directing tochoice: dispatch granularity belongs to the engine, uniform per payload, sojust<copack<Ts...>>would behave exactly aschoice<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 makesjuststructurally single-branch. Leaning: supportjust<void>— with deduction,just{}is a much nicer spelling of "nothing special here, but it's a success" thanexpected<void, copack<>>{}(and matchesstd::execution::just()sending zero values); to be confirmed in the PR.some_choice<T> || some_just<T> || (some_expected<T> && empty_copack<error_type>); public spelling to bikeshed here (some_identity?).and_thenfunctor 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 allchoice→ 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 overvalue()(forexpected<copack<...>, copack<>>that is a copack, the very same engine callchoice::and_thenmakes over its own payload). No carrier header learns another carrier: the verb layer already names them all and is the licensed cross-carrier place.and_thenis 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_elsehas no cluster counterpart:choiceandjusthave no error surface, andexpected<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, thejustmandate 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.