Skip to content

Re-found choice as an alias of just over a copack #414

Description

@Bronek

Proposal: make choice<Ts...> an alias template for just<copack<Ts...>>. just legalizes copack payloads through two specializations: one for a non-empty copack payload, carrying the branch-wise machinery found in today's choice; and one for every other payload, carrying today's just machinery. just<copack<>> is declared but never defined: a computation that must select one of zero alternatives is absurd, just like today's incomplete choice<>. Code requiring a unit carrier spells it just<void> (the identity-cluster analogue of fn::expected_unit).

Motivation. Today, the identity carrier's fmap is partial: the just::transform member rejects a copack-returning callback (because of the just.hpp payload mandate), while the pipeline verb repairs that rejection through promotion. In transform.hpp, _promoted_choice / applicable_transform_promote change the carrier family within what is categorically a plain functor map. The alias makes the identity functor genuinely total: a copack-returning map lands on just<copack<A, B>>, which is the choice carrier, with neither a carrier crossing nor a special case. The promotion machinery is deleted outright, and some_identity loses a disjunct.

This is not #321. That issue (closed as a no-go) proposed merging the monad into the payload, erasing the two-level algebra. This proposal preserves both levels—copack as the self-flattening payload and the identity monad on top—and moves only the nominal normalization boundary, from choice-the-class to just-the-class. The interaction table survives unchanged:

expression behaviour under the alias
copack-in-copack flattens (maker splices) unchanged
copack-in-choice flattens inside the boundary choice_for<copack<A,B>, C> = just<copack_for<A,B,C>> — the splice happens in copack_for, inside just
choice-in-copack nests (atom) copack_for<A, just<copack<B,C>>> — a just is not a copack; nests
choice-in-choice nests (the copack between blocks the join) choice<choice<X>> = just<copack<just<copack<X>>>> — copack intervenes; nests

It also reverses the #350 partition mandate on the basis of that mandate's own rationale: "just<copack<Ts...>> would behave exactly as choice<Ts...>". The alias accepts that behavioural identity and turns it from a reason for prohibition into a definitional identity. One meaning retains one spelling; that spelling is now canonical rather than exclusive. choice / choice_for remain the recommended way—but no longer the only way—to name this carrier.

Nested identity spellings become precise. just<choice<A,B>> = just<just<copack<A,B>>>: the identity layers are adjacent, so and_then with the identity Kleisli arrow strips them via the monadic join. By contrast, choice<choice<X>> = just<copack<just<copack<X>>>>: a copack intervenes, making the inner carrier an atom alternative, so the same identity bind instead flattens it through the branch-wise superset splice. One join, two licensed mechanisms. This removes the sole boundary at which join ∘ transform and and_then currently disagree, which is why this issue is sequenced before the fn::join issue.

Consequences — breaking, deliberate, to be documented explicitly in the 0.2.0 release notes:

  • choice::value() returns the copack payload (today it returns *this, an artifact of inheritance).
  • The derived-to-base choice&copack& conversions disappear; the nominal boundary becomes airtight. For usability, a small const-query surface is forwarded. The general shape is: has_value(in_place_type<T>), yes; get_ptr, no; mutators, no. The exact set is settled during implementation, favoring usability over complexity.
  • Diagnostics spell the canonical just<copack<...>>; library-authored messages may still say "choice".
  • just is no longer structurally single-branch: its dispatch granularity is the engine's, uniform per payload. Generic code constrained on some_just must be audited for a single-branch assumption before this lands.
  • ABI: covered by the 0.2 inline-namespace bump.

Documentation: TYPE_ALGEBRA.md §3 (the just<copack<...>> note reverses direction), while §10 and §11 simplify. Add a dated CHANGELOG.md entry recording what this obsoletes and why.

Assisted-by: Claude:claude-fable-5

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestrelease-0.2Planned for release 0.2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions