diff --git a/docs/choice/index.md b/docs/choice/index.md index dad18d15..7d1af1fa 100644 --- a/docs/choice/index.md +++ b/docs/choice/index.md @@ -9,18 +9,19 @@ title: Choice monad :include-doxygen-doc: fn::choice ## choice_for {style: "api"} -Used to generate `fn::choice` correctly. Sorts and deduplicates types under the hood. - -Some text explaining this section... +The construction alias: accepts alternatives in any order, with duplicates and nested copacks, +and resolves to the canonical `fn::choice`. Prefer it over spelling `choice` directly, so that no +spelling in your project is tied to one compiler's alternative order. :include-doxygen-member: fn::choice_for { signatureOnly: false, includeAllMatches: true } ## choice {style: "api"} -Some text explaining this section... +Construction: from a value of one alternative, in place from arguments, or widening from a +`copack` over a subset of the alternatives. :include-doxygen-member: fn::choice { signatureOnly: false, includeAllMatches: true } ## value {style: "api"} -Some text explaining this section... +The alternatives as the underlying `copack`, always present. :include-doxygen-member: fn::choice< Ts... >::value { signatureOnly: false, includeAllMatches: true } diff --git a/docs/composition/index.md b/docs/composition/index.md new file mode 100644 index 00000000..ad32abc2 --- /dev/null +++ b/docs/composition/index.md @@ -0,0 +1,26 @@ +--- +title: Composition +--- + +##### Defined in {style: "api", badge: "#include "} + +--- + +Independent computations compose side by side. The conjunction `a & b` keeps both results: +values multiply into a `pack` and errors sum into a `copack`, with the leftmost failing operand's +error held at runtime. The disjunction `a | b` keeps the first that worked: values sum into a +`copack` and errors multiply into a `pack`, present only when every operand failed. Each +carrier's header declares its own `&` and `|` operators; this header defines their n-ary folds +`fn::conjoin` and `fn::disjoin`. + +## conjoin {style: "api"} +:include-doxygen-doc: fn::conjoin_t + +## conjoin call signatures {style: "api"} +:include-doxygen-member: fn::conjoin_t::operator() { signatureOnly: false, includeAllMatches: true } + +## disjoin {style: "api"} +:include-doxygen-doc: fn::disjoin_t + +## disjoin call signatures {style: "api"} +:include-doxygen-member: fn::disjoin_t::operator() { signatureOnly: false, includeAllMatches: true } diff --git a/docs/copack/index.md b/docs/copack/index.md index 63cdcec1..84d71908 100644 --- a/docs/copack/index.md +++ b/docs/copack/index.md @@ -9,3 +9,14 @@ title: Copack :include-doxygen-doc: fn::copack :include-doxygen-doc: fn::copack_for + +## Apply {style: "api"} +Elimination: the active alternative routes into the callable, exhaustively - every alternative +must have a viable arm. + +:include-doxygen-member: fn::copack< Ts... >::apply { signatureOnly: false, includeAllMatches: true } + +## Transform {style: "api"} +The self-flattening map over the alternatives: the branch results form a new normalized copack. + +:include-doxygen-member: fn::copack< Ts... >::transform { signatureOnly: false, includeAllMatches: true } diff --git a/docs/expected/index.md b/docs/expected/index.md new file mode 100644 index 00000000..8fff1cb7 --- /dev/null +++ b/docs/expected/index.md @@ -0,0 +1,25 @@ +--- +title: Expected monad +--- + +##### Defined in {style: "api", badge: "#include "} + +--- + +:include-doxygen-doc: fn::expected + +## expected_unit {style: "api"} +The graded gateway: initiating a pipeline with this unit trigger opts all subsequent `and_then` +steps into graded error-set unioning, with no fake starting errors. + +:include-doxygen-member: fn::expected_unit { signatureOnly: false, includeAllMatches: true } + +## copack_error {style: "api"} +The explicit lift into the graded world, on the error side. + +:include-doxygen-member: fn::expected::copack_error { signatureOnly: false, includeAllMatches: true } + +## copack_value {style: "api"} +The same lift, on the value side. + +:include-doxygen-member: fn::expected::copack_value { signatureOnly: false, includeAllMatches: true } diff --git a/docs/index.md b/docs/index.md index 5d04f223..8bf56bf4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,21 +4,21 @@ Functional programming in C++ ## Why -The purpose of this library is to exercise an approach to functional programming in C++ on top of the existing `std` C++ `std::expected` and `std::optional`, with the aim of eventually extending the future versions of the C++ standard library with the functionality found to work well. +The purpose of this library is to exercise an approach to functional programming in C++ on top of the existing standard vocabulary types (such as `std::expected` and `std::optional`), with the aim of eventually extending future revisions of the C++ standard library with the functionality found to work well. ## How -The approach is to take the existing `std` types in the C++ standard library (when appropriate) and extend them (via inheritance) with the facilities useful in writing functional style programs. Eventually, the proposed functionality will be (hopefully) folded into the existing `std` types and new `std` types will be added. +The library comes as two parts: `pfn` (namespace `pfn`) is a faithful polyfill of the standard vocabulary types as specified for C++26, available to a C++20 compiler; `fn` (namespace `fn`) extends them with the facilities useful in writing functional style programs and adds new vocabulary types. Every `fn` type with a `pfn` counterpart is a strict superset of it: switching a valid program from `pfn` types to `fn` changes neither compilation nor behaviour. ## What -The library provides following utilities: +The library provides the following utilities: * functors - extensible system of encapsulation of monadic operations, expressed with a pipe `operator |` * copack - coproduct of types (a sum of types), similar to `std::variant` but indexed by type rather than order, composes with the product of types * choice monad - monad built on top of the coproduct of types, dispatch by overloading rules * pack - product of types, similar to `std::tuple`, composes with the coproduct of types -* composition - monadic types containing arbitrary types, products or coproducts can be combined with `operator &` +* composition - monadic computations combined side by side: conjunction with `operator &`, disjunction with `operator |`, and their n-ary folds `fn::conjoin` and `fn::disjoin` * multidispatch - dispatch any valid combination of product(s) and coproduct(s) to a function, based on overloading rules * graded monad - integrate coproduct into `optional` and `expected` monads, enables extensible `expected` error types * ... and more diff --git a/docs/just/index.md b/docs/just/index.md index b23edd44..e7a6b197 100644 --- a/docs/just/index.md +++ b/docs/just/index.md @@ -7,3 +7,14 @@ title: Just --- :include-doxygen-doc: fn::just + +## value {style: "api"} +The payload, always present: the access is total, never throwing. + +:include-doxygen-member: fn::just::value { signatureOnly: false, includeAllMatches: true } + +## transform {style: "api"} +:include-doxygen-member: fn::just::transform { signatureOnly: false, includeAllMatches: true } + +## and_then {style: "api"} +:include-doxygen-member: fn::just::and_then { signatureOnly: false, includeAllMatches: true } diff --git a/docs/multidispatch/index.md b/docs/multidispatch/index.md new file mode 100644 index 00000000..3ac466c7 --- /dev/null +++ b/docs/multidispatch/index.md @@ -0,0 +1,24 @@ +--- +title: Multidispatch +--- + +##### Defined in {style: "api", badge: "#include "} + +--- + +Elimination of the algebraic structures: `fn::apply` unpacks products and dispatches over +alternatives by ordinary C++ overload resolution, and `fn::overload` fuses per-alternative +lambdas into one overload set. Dispatch is exhaustive: an alternative without a viable arm makes +the whole call not applicable. + +## apply {style: "api"} +:include-doxygen-member: fn::apply { signatureOnly: false, includeAllMatches: true } + +## apply_r {style: "api"} +:include-doxygen-member: fn::apply_r { signatureOnly: false, includeAllMatches: true } + +## overload {style: "api"} + +##### Defined in {style: "api", badge: "#include "} + +:include-doxygen-doc: fn::overload diff --git a/docs/optional/index.md b/docs/optional/index.md new file mode 100644 index 00000000..8b9f4fd7 --- /dev/null +++ b/docs/optional/index.md @@ -0,0 +1,14 @@ +--- +title: Optional monad +--- + +##### Defined in {style: "api", badge: "#include "} + +--- + +:include-doxygen-doc: fn::optional + +## copack_value {style: "api"} +The explicit lift into the graded world. + +:include-doxygen-member: fn::optional::copack_value { signatureOnly: false, includeAllMatches: true } diff --git a/docs/optional/transform_error.md b/docs/optional/transform_error.md index 084ed0d8..69d7822b 100644 --- a/docs/optional/transform_error.md +++ b/docs/optional/transform_error.md @@ -16,4 +16,5 @@ title: fn::transform_error --- ## Return value {style: "api"} -A monadic type of the same kind. +Not applicable: `transform_error` is rejected on `optional`, which has no error value to map. +Use `or_else` to act on the empty state instead. diff --git a/docs/pack/index.md b/docs/pack/index.md index 352af83d..8af126c3 100644 --- a/docs/pack/index.md +++ b/docs/pack/index.md @@ -9,9 +9,10 @@ title: Packs :include-doxygen-doc: fn::pack ## Append {style: "api"} -Some text explaining this section... +Grows the product without nesting: appending a value adds one field, and appending a pack +splices its fields in. :include-doxygen-member: fn::pack::append { signatureOnly: false, includeAllMatches: true } ## Apply {style: "api"} -Some text explaining this section... +Elimination: the elements spread into a callable as separate arguments. :include-doxygen-member: fn::pack::apply { signatureOnly: false, includeAllMatches: true } diff --git a/docs/pfn/expected.md b/docs/pfn/expected.md new file mode 100644 index 00000000..0ed89322 --- /dev/null +++ b/docs/pfn/expected.md @@ -0,0 +1,28 @@ +--- +title: Expected polyfill +--- + +##### Defined in {style: "api", badge: "#include "} + +--- + +:include-doxygen-doc: pfn::expected + +## expected over void {style: "api"} +The partial specialization serving computations which succeed with no value. + +:include-doxygen-doc: pfn::expected< void, E > + +## unexpected {style: "api"} + +:include-doxygen-doc: pfn::unexpected + +## unexpect {style: "api"} + +:include-doxygen-doc: pfn::unexpect_t + +## bad_expected_access {style: "api"} + +:include-doxygen-doc: pfn::bad_expected_access + +:include-doxygen-doc: pfn::bad_expected_access< void > diff --git a/docs/pfn/index.md b/docs/pfn/index.md new file mode 100644 index 00000000..b568d009 --- /dev/null +++ b/docs/pfn/index.md @@ -0,0 +1,25 @@ +--- +title: Polyfill layer +--- + +The library is layered: namespace `pfn` is a faithful polyfill of standard vocabulary types and +utilities as specified for C++26, available to a C++20 compiler, and namespace `fn` builds the +functional-programming extensions on top of it. Every `fn` type with a `pfn` counterpart is a +strict superset of it: a valid program switching from `pfn` to `fn` changes neither compilation +nor behaviour. + +`pfn` polyfills only what C++20 lacks: all of ``, the C++23 and C++26 additions to +`std::optional` (the monadic operations, iterator support, `optional`), `std::apply` in its +SFINAE-friendly C++26 shape together with its applicability traits, `std::invoke_r` and +`std::unreachable`. Names C++20 already has — `std::nullopt`, `std::in_place`, +`std::bad_optional_access` — are used directly and not mirrored. + +The polyfills track the C++ working draft, deviating deliberately in three ways, each noted on +the entity it concerns: + +* where the standard leaves a member's `noexcept` specification unstated, one is derived from + the underlying types; every such clause is marked `// extension` in the source +* the draft's hardened preconditions are checked with an assertion, customizable by defining + `LIBFN_ASSERT` before inclusion +* `expected`'s comparison against a value is declared at namespace scope rather than as a + hidden friend, keeping its constraint deducible diff --git a/docs/pfn/optional.md b/docs/pfn/optional.md new file mode 100644 index 00000000..5c90b7b3 --- /dev/null +++ b/docs/pfn/optional.md @@ -0,0 +1,13 @@ +--- +title: Optional polyfill +--- + +##### Defined in {style: "api", badge: "#include "} + +--- + +:include-doxygen-doc: pfn::optional + +## optional over a reference {style: "api"} + +:include-doxygen-doc: pfn::optional< T & > diff --git a/docs/pfn/utility.md b/docs/pfn/utility.md new file mode 100644 index 00000000..c4a095ef --- /dev/null +++ b/docs/pfn/utility.md @@ -0,0 +1,31 @@ +--- +title: Utilities +--- + +## apply {style: "api"} + +##### Defined in {style: "api", badge: "#include "} + +:include-doxygen-member: pfn::apply { signatureOnly: false, includeAllMatches: true } + +### Applicability traits {style: "api"} +The C++26 traits `apply` is specified through; each also comes in its `_v` (for the two +predicates) or `_t` (for the result) form. + +:include-doxygen-doc: pfn::is_applicable + +:include-doxygen-doc: pfn::is_nothrow_applicable + +:include-doxygen-doc: pfn::apply_result + +## invoke_r {style: "api"} + +##### Defined in {style: "api", badge: "#include "} + +:include-doxygen-member: pfn::invoke_r { signatureOnly: false, includeAllMatches: true } + +## unreachable {style: "api"} + +##### Defined in {style: "api", badge: "#include "} + +:include-doxygen-member: pfn::unreachable { signatureOnly: false, includeAllMatches: true } diff --git a/docs/toc b/docs/toc index 5dac6bcc..df0f2841 100644 --- a/docs/toc +++ b/docs/toc @@ -1,4 +1,5 @@ expected + index and_then discard fail @@ -11,6 +12,7 @@ expected transform value_or optional + index and_then discard fail @@ -32,5 +34,14 @@ copack index just index +composition + index +multidispatch + index +pfn + index + expected + optional + utility ci index diff --git a/include/fn/and_then.hpp b/include/fn/and_then.hpp index ae3e73d2..1648bf52 100644 --- a/include/fn/and_then.hpp +++ b/include/fn/and_then.hpp @@ -166,10 +166,11 @@ constexpr inline struct and_then_t final { struct and_then_t::apply final { /** - * @brief TODO + * @brief Binds through the carrier's own `and_then` member * * @param v The monad * @param fn The function to apply + * @return A carrier of the same kind, possibly with a widened error grade */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const // diff --git a/include/fn/choice.hpp b/include/fn/choice.hpp index d10ae3b8..62ac1749 100644 --- a/include/fn/choice.hpp +++ b/include/fn/choice.hpp @@ -22,9 +22,9 @@ namespace fn { inline namespace LIBFN_VERSION { /** - * @brief TODO + * @brief Checks if a type is a `choice` (with any alternatives) * - * @tparam T TODO + * @tparam T Type to check, possibly cv-ref qualified */ template concept some_choice = detail::_some_choice; @@ -42,9 +42,17 @@ static constexpr bool _is_valid_choice_subtype // } /** - * @brief TODO + * @brief The identity carrier over a coproduct: always holds one of the alternatives * - * @tparam Ts TODO + * A never-failing computation whose result is one of `Ts...` - the one canonical spelling of that + * shape, as `just` over a copack is rejected. Where a bare `copack` is self-flattening data, a + * `choice` is an atom: mapping keeps a returned choice whole, and only `and_then` joins the + * branches' choices away into the normalized superset. The alternatives obey the same canonical + * form as `copack`'s - flat, unique, sorted - so spell `choice_for`; `choice<>` is deliberately + * incomplete, an always-present alternative needing at least one alternative to exist. A + * structural type when its alternatives are. + * + * @tparam Ts The alternatives - flat, unique and sorted in the canonical order */ template requires(sizeof...(Ts) > 0) @@ -58,12 +66,6 @@ struct choice : copack { template <::std::size_t I> using select_nth = detail::select_nth_t; template static constexpr bool has_type = _impl::template has_type; - /** - * @brief TODO - * - * @tparam Ret TODO - * @param fn TODO - */ template [[nodiscard]] constexpr auto _invoke(auto &&fn) const & noexcept(detail::_is_nothrow_rtst_invocable) @@ -71,12 +73,6 @@ struct choice : copack { return detail::invoke_type_variadic_union(this->data, this->index, FWD(fn)); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @param fn TODO - */ template [[nodiscard]] constexpr auto _invoke(auto &&fn) && noexcept(detail::_is_nothrow_rtst_invocable) @@ -86,10 +82,9 @@ struct choice : copack { } /** - * @brief TODO + * @brief Constructs the alternative matching the value's decayed type * - * @tparam T TODO - * @param v TODO + * @param v Value of one alternative */ template constexpr choice(T &&v) // NOSONAR cpp:S1709,S6458 implicit arm of the explicit pair; has_type excludes self @@ -102,10 +97,10 @@ struct choice : copack { } /** - * @brief TODO + * @brief Constructs the alternative matching the value's decayed type, where that conversion is + * explicit * - * @tparam T TODO - * @param v TODO + * @param v Value of one alternative */ template constexpr explicit choice(T &&v) // NOSONAR cpp:S6458 has_type excludes self @@ -118,11 +113,11 @@ struct choice : copack { } /** - * @brief TODO + * @brief Constructs the alternative `T` in place from the arguments * - * @tparam T TODO - * @param d TODO - * @param v TODO + * @tparam T The alternative to construct + * @param d Tag naming the alternative + * @param args Arguments to construct the alternative from */ template constexpr explicit choice(::std::in_place_type_t d, auto &&...args) // @@ -133,10 +128,12 @@ struct choice : copack { } /** - * @brief TODO + * @brief Widening constructor from a copack over a subset of the alternatives + * + * A copack converts implicitly into any choice that can hold its alternatives: the lift from + * data to the never-failing carrier over it. * - * @tparam Tx TODO - * @param v TODO + * @param v The copack to lift */ template constexpr choice(copack const &v) // NOSONAR cpp:S1709 implicit widening by design @@ -147,12 +144,6 @@ struct choice : copack { { } - /** - * @brief TODO - * - * @tparam Tx TODO - * @param v TODO - */ template constexpr choice(copack &&v) // NOSONAR cpp:S1709 implicit widening by design noexcept(::std::is_nothrow_constructible_v<_impl, ::std::in_place_type_t>, copack>) @@ -163,10 +154,9 @@ struct choice : copack { } /** - * @brief TODO + * @brief Widening constructor from a copack whose type is spelled as a tag * - * @tparam Tx TODO - * @param v TODO + * @param v The copack to lift */ template constexpr choice(::std::in_place_type_t>, some_copack auto &&v) // @@ -222,41 +212,27 @@ struct choice : copack { } /** - * @brief TODO + * @brief Accesses the alternatives as the underlying `copack` * - * @return TODO - */ - [[nodiscard]] constexpr value_type &value() & noexcept { return *this; } - - /** - * @brief TODO + * Always present - a choice cannot fail - so the access is total, never throwing. * - * @return TODO + * @return Reference to `*this` as its `copack` base, in `*this`'s value category */ + [[nodiscard]] constexpr value_type &value() & noexcept { return *this; } [[nodiscard]] constexpr value_type const &value() const & noexcept { return *this; } - - /** - * @brief TODO - * - * @return TODO - */ [[nodiscard]] constexpr value_type &&value() && noexcept { return ::std::move(*this); } - - /** - * @brief TODO - * - * @return TODO - */ [[nodiscard]] constexpr value_type const &&value() const && noexcept { return ::std::move(*this); } /** - * @brief TODO + * @brief Eliminates the choice: the active alternative routes into the callable * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO + * Exactly `copack`'s `apply`, over the alternatives: exhaustive dispatch by ordinary overload + * resolution, one deduced result type, a tuple-like alternative unpacked one level into its + * elements, trailing arguments after the content. + * + * @param fn Callable applied on the active alternative; `fn::overload` fuses arms into one + * @param args Additional arguments, appended after the alternative's content + * @return The callable's result */ template [[nodiscard]] constexpr auto apply(Fn &&fn, Args &&...args) & noexcept( @@ -269,15 +245,6 @@ struct choice : copack { return detail::apply_variadic_union(_impl::data, _impl::index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply(Fn &&fn, Args &&...args) const & noexcept( detail::_is_nothrow_rts_applicable< @@ -291,15 +258,6 @@ struct choice : copack { return detail::apply_variadic_union(_impl::data, _impl::index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply(Fn &&fn, Args &&...args) && noexcept( detail::_is_nothrow_rts_applicable : copack { FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply(Fn &&fn, Args &&...args) const && noexcept( detail::_is_nothrow_rts_applicable< @@ -336,14 +285,12 @@ struct choice : copack { } /** - * @brief TODO + * @brief Eliminates the choice, converting each branch's result to `T` * - * @tparam T TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO + * @tparam T Type the results convert to + * @param fn Callable applied on the active alternative + * @param args Additional arguments, appended after the alternative's content + * @return The callable's result, converted to `T` */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, Args &&...args) & noexcept( @@ -356,16 +303,6 @@ struct choice : copack { return detail::apply_variadic_union(_impl::data, _impl::index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam T TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, Args &&...args) const & noexcept( detail::_is_nothrow_rts_applicable< @@ -377,16 +314,6 @@ struct choice : copack { return detail::apply_variadic_union(_impl::data, _impl::index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam T TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, Args &&...args) && noexcept( detail::_is_nothrow_rts_applicable< @@ -399,16 +326,6 @@ struct choice : copack { FWD(args)...); } - /** - * @brief TODO - * - * @tparam T TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, Args &&...args) const && noexcept( detail::_is_nothrow_rts_applicable< @@ -422,13 +339,14 @@ struct choice : copack { } /** - * @brief TODO + * @brief Eliminates the choice, keyed by the alternative's type + * + * The active arm receives `std::in_place_type` for the alternative held, followed by its + * content as `copack`'s `apply_type` hands it over. * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO + * @param fn Callable applied on the tag and the alternative's content + * @param args Additional arguments, appended after the content + * @return The callable's result */ template [[nodiscard]] constexpr auto apply_type(Fn &&fn, Args &&...args) & noexcept( @@ -446,15 +364,6 @@ struct choice : copack { _impl::data, _impl::index, detail::_apply_type_fn{FWD(fn)}, FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type(Fn &&fn, Args &&...args) const & noexcept( detail::_is_nothrow_rtst_invocable< @@ -471,15 +380,6 @@ struct choice : copack { _impl::data, _impl::index, detail::_apply_type_fn{FWD(fn)}, FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type(Fn &&fn, Args &&...args) && noexcept( detail::_is_nothrow_rtst_invocable< @@ -496,15 +396,6 @@ struct choice : copack { ::std::move(_impl::data), _impl::index, detail::_apply_type_fn{FWD(fn)}, FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type(Fn &&fn, Args &&...args) const && noexcept( detail::_is_nothrow_rtst_invocable< @@ -522,14 +413,12 @@ struct choice : copack { } /** - * @brief TODO + * @brief Eliminates the choice, keyed by the alternative's type, converting the result to `Ret` * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO + * @tparam Ret Type the results convert to + * @param fn Callable applied on the tag and the alternative's content + * @param args Additional arguments, appended after the content + * @return The callable's result, converted to `Ret` */ template [[nodiscard]] constexpr auto apply_type_r(Fn &&fn, Args &&...args) & noexcept( @@ -541,16 +430,6 @@ struct choice : copack { _impl::data, _impl::index, detail::_apply_type_fn{FWD(fn)}, FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type_r(Fn &&fn, Args &&...args) const & noexcept( detail::_is_nothrow_rtst_invocable, choice const &, Args &&...>) -> Ret @@ -561,16 +440,6 @@ struct choice : copack { _impl::data, _impl::index, detail::_apply_type_fn{FWD(fn)}, FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type_r(Fn &&fn, Args &&...args) && noexcept( detail::_is_nothrow_rtst_invocable, choice &&, Args &&...>) -> Ret @@ -581,16 +450,6 @@ struct choice : copack { ::std::move(_impl::data), _impl::index, detail::_apply_type_fn{FWD(fn)}, FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type_r(Fn &&fn, Args &&...args) const && noexcept( detail::_is_nothrow_rtst_invocable, choice const &&, Args &&...>) -> Ret @@ -603,11 +462,14 @@ struct choice : copack { // NOTE Monadic operations, only `and_then` and `transform` are supported /** - * @brief TODO + * @brief Maps the alternatives, the branch results forming a new normalized choice + * + * As `copack`'s `transform`, staying a carrier: the branch results flatten, deduplicate and + * sort. A returned `choice` stays whole - an atom, nested as one alternative - where a returned + * bare `copack` dissolves into the set. * - * @tparam Fn TODO - * @param fn TODO - * @return TODO + * @param fn Callable applied on the active alternative; `fn::overload` fuses arms into one + * @return A choice of the normalized branch-result set, holding the active branch's result */ template [[nodiscard]] constexpr auto transform(Fn &&fn) & noexcept( @@ -620,13 +482,6 @@ struct choice : copack { return detail::apply_variadic_union(_impl::data, _impl::index, FWD(fn)); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @param fn TODO - * @return TODO - */ template [[nodiscard]] constexpr auto transform(Fn &&fn) const & noexcept( detail::_is_nothrow_rts_applicable< @@ -639,13 +494,6 @@ struct choice : copack { return detail::apply_variadic_union(_impl::data, _impl::index, FWD(fn)); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @param fn TODO - * @return TODO - */ template [[nodiscard]] constexpr auto transform(Fn &&fn) && noexcept( detail::_is_nothrow_rts_applicable< @@ -657,13 +505,6 @@ struct choice : copack { return detail::apply_variadic_union(::std::move(_impl::data), _impl::index, FWD(fn)); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @param fn TODO - * @return TODO - */ template [[nodiscard]] constexpr auto transform(Fn &&fn) const && noexcept( detail::_is_nothrow_rts_applicable< @@ -677,11 +518,15 @@ struct choice : copack { } /** - * @brief TODO + * @brief Binds the alternatives: every branch returns a `choice`, joined into the superset * - * @tparam Fn TODO - * @param fn TODO - * @return TODO + * The member is the carrier's own bind: each branch's returned choice splices its alternatives + * into one normalized superset choice. A branch returning a bare value belongs to `transform` + * instead, and is rejected with a named diagnostic; branches converging on another carrier kind + * belong to the pipeline `fn::and_then`, the licensed cross-carrier place. + * + * @param fn Callable applied on the active alternative; `fn::overload` fuses arms into one + * @return The normalized superset choice of the branches' alternatives */ template constexpr auto and_then(Fn &&fn) & noexcept( @@ -696,13 +541,6 @@ struct choice : copack { return detail::apply_variadic_union(_impl::data, _impl::index, FWD(fn)); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @param fn TODO - * @return TODO - */ template constexpr auto and_then(Fn &&fn) const & noexcept( detail::_is_nothrow_rts_applicable, @@ -717,13 +555,6 @@ struct choice : copack { return detail::apply_variadic_union(_impl::data, _impl::index, FWD(fn)); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @param fn TODO - * @return TODO - */ template constexpr auto and_then(Fn &&fn) && noexcept( detail::_is_nothrow_rts_applicable< @@ -737,13 +568,6 @@ struct choice : copack { return detail::apply_variadic_union(::std::move(_impl::data), _impl::index, FWD(fn)); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @param fn TODO - * @return TODO - */ template constexpr auto and_then(Fn &&fn) const && noexcept( detail::_is_nothrow_rts_applicable, @@ -937,13 +761,14 @@ template explicit choice(::std::in_place_type_t, auto &&...) -> template explicit choice(T) -> choice; /** - * @brief TODO + * @brief Compares two choices: equal when they hold the same alternative type with equal values * - * @tparam Ts TODO - * @tparam Tx TODO - * @param lh TODO - * @param rh TODO - * @return TODO + * The alternative sets need not match; an alternative the other side cannot hold compares unequal + * without being compared. + * + * @param lh Left choice + * @param rh Right choice + * @return Whether the active alternatives are the same type with equal values */ template [[nodiscard]] constexpr bool operator==(choice const &lh, choice const &rh) noexcept @@ -960,13 +785,11 @@ template } /** - * @brief TODO + * @brief The negation of `==` for two choices * - * @tparam Ts TODO - * @tparam Tx TODO - * @param lh TODO - * @param rh TODO - * @return TODO + * @param lh Left choice + * @param rh Right choice + * @return Whether the choices differ in alternative type or value */ template [[nodiscard]] constexpr bool operator!=(choice const &lh, choice const &rh) noexcept @@ -976,9 +799,13 @@ template } /** - * @brief TODO + * @brief Builds the canonical `choice` for any list of types + * + * The construction alias over `choice`, exactly as `copack_for` stands to `copack`: flattens, + * deduplicates and sorts into the canonical order. Spell `choice_for` rather than `choice`, so + * that no spelling in your project is tied to one compiler's alternative order. * - * @tparam Ts TODO + * @tparam Ts Types to combine - alternatives and copacks of them, in any order, duplicates allowed */ template using choice_for diff --git a/include/fn/concepts.hpp b/include/fn/concepts.hpp index 64d9b29b..e9b85bf6 100644 --- a/include/fn/concepts.hpp +++ b/include/fn/concepts.hpp @@ -43,12 +43,16 @@ concept _relocatable // `return FWD(v);` - the whole monad, hence both sides } // namespace detail /** - * @brief TODO - * @note `same_kind` is a fundamental concept in category theory; it allows - * transformation of a value_type, but not an error_type (where applicable) + * @brief Checks if two carriers are of the same monadic family, with compatible error sides * - * @tparam T TODO - * @tparam U TODO + * What `and_then` and the other success-path operations hold a callback's result to: the value + * side may change freely, the family may not. For `expected` the error sides must also agree - + * identical plain types, any two graded (copack) sides, or a plain side meeting its own singular + * lift `copack`, either way round. Any two `optional`s, any two `choice`s and any two `just`s + * are the same kind. + * + * @tparam T Carrier type, possibly cv-ref qualified + * @tparam U Carrier type, possibly cv-ref qualified */ template concept same_kind @@ -71,11 +75,15 @@ concept same_kind || (some_just && some_just); /** - * @brief TODO - * @note symmetrical to the above + * @brief The mirror of `same_kind`: the value side is pinned and the error side may vary + * + * What the error-side operations hold a callback's result to. Value sides agree when identical, + * when both are graded (copack), or - on `expected` - when a plain non-void side meets its own + * singular lift `copack`, either way round. Any two `choice`s qualify; never two carriers of + * different families. * - * @tparam T TODO - * @tparam U TODO + * @tparam T Carrier type, possibly cv-ref qualified + * @tparam U Carrier type, possibly cv-ref qualified */ template concept same_value_kind @@ -103,18 +111,24 @@ concept same_value_kind || (some_choice && some_choice); /** - * @brief TODO + * @brief Checks if two carriers agree on family, value side and error side alike + * + * The conjunction of `same_kind` and `same_value_kind`. * - * @tparam T TODO - * @tparam U TODO + * @tparam T Carrier type, possibly cv-ref qualified + * @tparam U Carrier type, possibly cv-ref qualified */ template concept same_monadic_type_as = same_kind && same_value_kind; /** - * @brief TODO + * @brief Checks if `fn::unexpected` over the decayed type can be built from the value by a cast * - * @tparam T TODO + * A `void` answers false - `unexpected` is ill-formed to instantiate, and the concept + * answers instead. An immovable value answers false as well: there is nothing to move or copy + * into the carrier. + * + * @tparam T Type of the value, possibly cv-ref qualified */ // The void conjunct is load-bearing: `unexpected` is ill-formed by a class-body mandate, // which fires during instantiation - outside any immediate context - so without it the question @@ -126,10 +140,13 @@ concept convertible_to_unexpected = (not ::std::is_void_v) && requires { }; /** - * @brief TODO + * @brief Checks if an `expected` over the decayed type can be built from the value by a cast + * + * Unlike the other `convertible_to_*` concepts here, `void` is admitted outright by its own arm: + * `expected` is a legitimate carrier with no value to convert. * - * @tparam T TODO - * @tparam E TODO + * @tparam T Type of the value, possibly cv-ref qualified + * @tparam E Error type of the target `expected` */ template concept convertible_to_expected @@ -137,9 +154,12 @@ concept convertible_to_expected || (::std::is_void_v); /** - * @brief TODO + * @brief Checks if an `optional` over the decayed type can be built from the value by a cast * - * @tparam T TODO + * A `void` answers false - `optional` is ill-formed to instantiate, and the concept answers + * instead. + * + * @tparam T Type of the value, possibly cv-ref qualified */ // The same load-bearing void conjunct as `convertible_to_unexpected`'s: `optional` and // `choice` (below) are likewise ill-formed to instantiate, by a class-body mandate. @@ -148,9 +168,12 @@ concept convertible_to_optional = (not ::std::is_void_v) && requires { static_cast>>(::std::declval()); }; /** - * @brief TODO + * @brief Checks if a `choice` over the decayed type can be built from the value by a cast + * + * A `void` answers false - `choice` is ill-formed to instantiate, and the concept answers + * instead. * - * @tparam T TODO + * @tparam T Type of the value, possibly cv-ref qualified */ template concept convertible_to_choice @@ -195,9 +218,12 @@ template concept some_identity = some_choice || some_just || some_empty_error; /** - * @brief TODO + * @brief Checks if the type casts to `bool` - what `filter` holds its predicate's result to * - * @tparam T TODO + * An explicit `operator bool` suffices: the concept casts, it does not ask for an implicit + * conversion. + * + * @tparam T Type to check, possibly cv-ref qualified */ template concept convertible_to_bool = requires { static_cast(::std::declval()); }; diff --git a/include/fn/copack.hpp b/include/fn/copack.hpp index 902fe51a..8e675672 100644 --- a/include/fn/copack.hpp +++ b/include/fn/copack.hpp @@ -23,25 +23,28 @@ namespace fn { inline namespace LIBFN_VERSION { /** - * @brief TODO + * @brief Checks if a type is a `copack` (with any alternatives, including none) * - * @tparam T TODO + * @tparam T Type to check, possibly cv-ref qualified */ template concept some_copack = detail::_some_copack; /** - * @brief TODO + * @brief Checks if a type is the empty `copack<>` - the uninhabited zero * - * @tparam T TODO + * A carrier side of this type is statically known never to hold a value, which is what renders + * the operations over that side vacuous. + * + * @tparam T Type to check, possibly cv-ref qualified */ template concept empty_copack = some_copack && (::std::remove_cvref_t::size == 0); /** - * @brief TODO + * @brief Checks if a type is a `std::in_place_type_t` tag * - * @tparam T TODO + * @tparam T Type to check, possibly cv-ref qualified */ template concept some_in_place_type = detail::_some_in_place_type; @@ -224,14 +227,26 @@ struct _copack_invoke_type_result<_collapsing_copack_tag, Fn, Self, Args...> fin } // namespace detail /** - * @brief TODO + * @brief The canonical coproduct payload: exactly one alternative is present + * + * A discriminated union indexed by type, not by position. The alternatives are required to be + * flat, unique and sorted in the library's total order over types, and an instantiation that + * diverges - out of order, duplicated, or nested - is ill-formed; spell `copack_for`, which + * normalizes any list into the canonical form, rather than name that order by hand. Every + * evaluation of a copack is an exhaustive dispatch: a callback set missing any alternative is + * rejected at compile time. A structural type when its alternatives are. * - * @tparam Ts TODO + * @tparam Ts The alternatives - flat, unique and sorted in the canonical order */ template struct copack; /** - * @brief A unit of copack<> monoid, cannot be created but offers useful static functions and can be put in an union + * @brief The empty copack: the algebra's zero, uninhabited by construction + * + * The deleted default constructor is the whole point: no value of this type can ever exist, so a + * carrier side declared `copack<>` is statically known never to be engaged. As the identity of + * the union it vanishes inside `copack_for`; the remaining special members are kept so that the + * type can sit inside a union storage. */ template <> struct copack<> final { constexpr copack() noexcept = delete; // NOTE, `= delete` here is the whole point @@ -246,9 +261,13 @@ template <> struct copack<> final { }; /** - * @brief TODO + * @brief The storage and operations of a non-empty copack * - * @tparam Ts TODO + * Holds one alternative in a variadic union together with its index. The special members are + * exactly as trivial, available and nothrow as the alternatives permit; mutation follows the + * standard's reinit discipline, with the strong exception guarantee and no valueless state. + * + * @tparam Ts The alternatives - flat, unique and sorted in the canonical order */ template requires(sizeof...(Ts) > 0) @@ -304,16 +323,16 @@ struct copack { && ::std::is_trivially_destructible_v)); /** - * @brief TODO + * @brief The `I`-th alternative in the canonical order * - * @tparam Ts I + * @tparam I Index of the alternative */ template <::std::size_t I> using select_nth = detail::select_nth_t; /** - * @brief TODO + * @brief Checks if `T` is one of the alternatives * - * @tparam T TODO + * @tparam T Type to look for */ template static constexpr bool has_type = data_t::template has_type; @@ -356,10 +375,12 @@ struct copack { } /** - * @brief TODO + * @brief Constructs the alternative matching the value's decayed type + * + * Takes a value of exactly one alternative: a merely convertible non-alternative is rejected, + * so interconvertible alternatives never make a resolution puzzle. * - * @tparam T TODO - * @param v TODO + * @param v Value of one alternative */ template constexpr copack(T &&v) // NOSONAR cpp:S1709,S6458 implicit arm of the explicit pair; has_type excludes self @@ -372,10 +393,10 @@ struct copack { } /** - * @brief TODO + * @brief Constructs the alternative matching the value's decayed type, where that conversion is + * explicit * - * @tparam T TODO - * @param v TODO + * @param v Value of one alternative */ template constexpr explicit copack(T &&v) // NOSONAR cpp:S6458 has_type excludes self @@ -388,10 +409,10 @@ struct copack { } /** - * @brief TODO + * @brief Constructs the alternative `T` in place from the arguments * - * @tparam T TODO - * @param args TODO + * @tparam T The alternative to construct + * @param args Arguments to construct the alternative from */ template constexpr explicit copack(::std::in_place_type_t, @@ -402,10 +423,12 @@ struct copack { } /** - * @brief TODO + * @brief Widening constructor from a copack over a subset of the alternatives * - * @tparam Tx TODO - * @param arg TODO + * The active alternative relocates into this copack; implicit, as the subset-to-superset + * direction can never lose information. + * + * @param arg The narrower copack */ template constexpr copack(copack const &arg) // NOSONAR cpp:S1709 implicit widening by design @@ -421,12 +444,6 @@ struct copack { { } - /** - * @brief TODO - * - * @tparam Tx TODO - * @param arg TODO - */ template constexpr copack(copack &&arg) // NOSONAR cpp:S1709 implicit widening by design noexcept((... && detail::_nothrow_makeable)) @@ -442,10 +459,9 @@ struct copack { } /** - * @brief TODO + * @brief Widening constructor from a copack whose type is spelled as a tag * - * @tparam Tx TODO - * @param arg TODO + * @param arg The narrower copack */ template constexpr copack(::std::in_place_type_t>, some_copack auto &&arg) // @@ -462,9 +478,9 @@ struct copack { } /** - * @brief TODO + * @brief Copy constructor; trivial where every alternative's is * - * @param other TODO + * @param other The copack to copy from */ constexpr copack(copack const &other) requires _trivially_copy_constructible @@ -481,9 +497,9 @@ struct copack { } /** - * @brief TODO + * @brief Move constructor; trivial where every alternative's is * - * @param other TODO + * @param other The copack to move from */ constexpr copack(copack &&other) requires _trivially_move_constructible @@ -579,8 +595,8 @@ struct copack { /** * @brief Copy assignment, with the strong exception guarantee * - * @param other TODO - * @return TODO + * @param other The copack to copy from + * @return Reference to `*this` */ // Assignment requires of every alternative its own `operator=`, and uses it when the incoming // alternative is the one already held; a different alternative is replaced by construction, as @@ -610,8 +626,8 @@ struct copack { /** * @brief Move assignment, with the strong exception guarantee * - * @param other TODO - * @return TODO + * @param other The copack to move from + * @return Reference to `*this` */ // The nothrow move construction is still demanded - `_reinit`'s replacement arm and `_reassign`'s // snapshot both rest on it - but the operator itself is only as nothrow as the alternatives' own @@ -640,8 +656,8 @@ struct copack { /** * @brief Widening copy assignment from a copack over a subset of the alternatives * - * @param arg TODO - * @return TODO + * @param arg The narrower copack + * @return Reference to `*this` */ // Constrained on the alternatives the source can actually deliver, like the widening // constructors: routing through construction and same-type assignment would let an uninvolved @@ -670,8 +686,8 @@ struct copack { /** * @brief Widening move assignment from a copack over a subset of the alternatives * - * @param arg TODO - * @return TODO + * @param arg The narrower copack + * @return Reference to `*this` */ template constexpr copack &operator=(copack &&arg) // @@ -692,8 +708,8 @@ struct copack { /** * @brief Assignment from a value of one alternative, with the strong exception guarantee * - * @param v TODO - * @return TODO + * @param v Value of one alternative + * @return Reference to `*this` */ // Takes a value of exactly one alternative, as the converting constructors do - never // std::variant's converting-assignment resolution, so a convertible non-alternative stays @@ -719,8 +735,8 @@ struct copack { * @brief Destroys the alternative held and constructs a `T` from the arguments, with the strong * exception guarantee * - * @tparam T TODO - * @param args TODO + * @tparam T The alternative to construct + * @param args Arguments to construct the new alternative from * @return Reference to the new alternative */ // The mutation path for alternatives that do not support assignment: destroy-and-reconstruct, @@ -741,10 +757,10 @@ struct copack { } /** - * @brief TODO + * @brief Checks if `T` is the active alternative * - * @tparam T TODO - * @return TODO + * @tparam T The alternative to ask about + * @return Whether `T` is the alternative held */ template requires has_type @@ -756,10 +772,13 @@ struct copack { } /** - * @brief TODO + * @brief Pointer to the alternative `T`, or `nullptr` where it is not the one held * - * @tparam T TODO - * @return TODO + * The escape hatch for direct access: unlike `apply`, no dispatch and no exhaustiveness - the + * caller names one alternative and tests the result. + * + * @tparam T The alternative to access + * @return Pointer to the alternative, or `nullptr` */ template requires has_type @@ -768,12 +787,6 @@ struct copack { return has_value(::std::in_place_type) ? detail::ptr_variadic_union(data) : nullptr; } - /** - * @brief TODO - * - * @tparam T TODO - * @return TODO - */ template requires has_type [[nodiscard]] constexpr T const *get_ptr(::std::in_place_type_t = ::std::in_place_type) const noexcept @@ -782,13 +795,16 @@ struct copack { } /** - * @brief TODO + * @brief Eliminates the copack: the active alternative routes into the callable * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO + * The dispatch is exhaustive - every alternative must have a viable arm, selected by ordinary + * overload resolution - and the result type is deduced, so all alternatives must yield the same + * one; `apply_r` serves where they differ. A tuple-like alternative is unpacked one level into + * its elements, and trailing arguments follow the content. + * + * @param fn Callable applied on the active alternative; `fn::overload` fuses arms into one + * @param args Additional arguments, appended after the alternative's content + * @return The callable's result */ template [[nodiscard]] constexpr auto apply(Fn &&fn, Args &&...args) & noexcept( @@ -802,15 +818,6 @@ struct copack { return detail::apply_variadic_union(this->data, index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply(Fn &&fn, Args &&...args) const & noexcept( detail::_is_nothrow_rts_applicable< @@ -823,15 +830,6 @@ struct copack { return detail::apply_variadic_union(this->data, index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply(Fn &&fn, Args &&...args) && noexcept( detail::_is_nothrow_rts_applicable< @@ -844,15 +842,6 @@ struct copack { return detail::apply_variadic_union(::std::move(*this).data, index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply(Fn &&fn, Args &&...args) const && noexcept( detail::_is_nothrow_rts_applicable { } /** - * @brief TODO + * @brief Eliminates the copack, converting each branch's result to `Ret` * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO + * The escape from result-type convergence: every alternative converts implicitly into its + * parent copack, so `apply_r` targeting a `copack_for` of the branch results accepts branches + * that disagree. + * + * @tparam Ret Type the results convert to + * @param fn Callable applied on the active alternative; `fn::overload` fuses arms into one + * @param args Additional arguments, appended after the alternative's content + * @return The callable's result, converted to `Ret` */ template [[nodiscard]] constexpr auto @@ -885,16 +876,6 @@ struct copack { return detail::apply_variadic_union(this->data, index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, Args &&...args) const & noexcept( detail::_is_nothrow_rts_applicable) -> Ret @@ -904,16 +885,6 @@ struct copack { return detail::apply_variadic_union(this->data, index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, Args &&...args) && noexcept(detail::_is_nothrow_rts_applicable) @@ -924,16 +895,6 @@ struct copack { return detail::apply_variadic_union(::std::move(*this).data, index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, Args &&...args) const && noexcept( detail::_is_nothrow_rts_applicable) -> Ret @@ -944,13 +905,16 @@ struct copack { } /** - * @brief TODO + * @brief Eliminates the copack, keyed by the alternative's type * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO + * The active arm receives `std::in_place_type` for the alternative held, followed by its + * content - a tuple-like alternative's elements form is the row's one signature - so the handler + * knows which injection placed the value, even where C++'s implicit conversions would conflate + * the payloads. + * + * @param fn Callable applied on the tag and the alternative's content + * @param args Additional arguments, appended after the content + * @return The callable's result */ template [[nodiscard]] constexpr auto apply_type(Fn &&fn, Args &&...args) & noexcept( @@ -968,15 +932,6 @@ struct copack { FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type(Fn &&fn, Args &&...args) const & noexcept( detail::_is_nothrow_rtst_invocable< @@ -993,15 +948,6 @@ struct copack { FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type(Fn &&fn, Args &&...args) && noexcept( detail::_is_nothrow_rtst_invocable< @@ -1018,15 +964,6 @@ struct copack { detail::_apply_type_fn{FWD(fn)}, FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type(Fn &&fn, Args &&...args) const && noexcept( detail::_is_nothrow_rtst_invocable< @@ -1044,14 +981,12 @@ struct copack { } /** - * @brief TODO + * @brief Eliminates the copack, keyed by the alternative's type, converting the result to `Ret` * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO + * @tparam Ret Type the results convert to + * @param fn Callable applied on the tag and the alternative's content + * @param args Additional arguments, appended after the content + * @return The callable's result, converted to `Ret` */ template [[nodiscard]] constexpr auto apply_type_r(Fn &&fn, Args &&...args) & noexcept( @@ -1063,16 +998,6 @@ struct copack { FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type_r(Fn &&fn, Args &&...args) const & noexcept( detail::_is_nothrow_rtst_invocable, copack const &, Args &&...>) -> Ret @@ -1083,16 +1008,6 @@ struct copack { FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type_r(Fn &&fn, Args &&...args) && noexcept( detail::_is_nothrow_rtst_invocable, copack &&, Args &&...>) -> Ret @@ -1103,16 +1018,6 @@ struct copack { detail::_apply_type_fn{FWD(fn)}, FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_type_r(Fn &&fn, Args &&...args) const && noexcept( detail::_is_nothrow_rtst_invocable, copack const &&, Args &&...>) -> Ret @@ -1124,13 +1029,15 @@ struct copack { } /** - * @brief TODO + * @brief Maps the alternatives, the branch results forming a new normalized copack + * + * The self-flattening map: the callable is dispatched exhaustively, and the branch results - + * heterogeneous types allowed, a copack result dissolving into the set - flatten, deduplicate + * and sort into the `copack_for` of them all. * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO + * @param fn Callable applied on the active alternative; `fn::overload` fuses arms into one + * @param args Additional arguments, appended after the alternative's content + * @return A copack of the normalized branch-result set, holding the active branch's result */ template [[nodiscard]] constexpr auto transform(Fn &&fn, Args &&...args) & noexcept( @@ -1144,15 +1051,6 @@ struct copack { return detail::apply_variadic_union(this->data, index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto transform(Fn &&fn, Args &&...args) const & noexcept( detail::_is_nothrow_rts_applicable { return detail::apply_variadic_union(this->data, index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto transform(Fn &&fn, Args &&...args) && noexcept( detail::_is_nothrow_rts_applicable< @@ -1186,15 +1075,6 @@ struct copack { return detail::apply_variadic_union(::std::move(*this).data, index, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @tparam Args TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto transform(Fn &&fn, Args &&...args) const && noexcept( detail::_is_nothrow_rts_applicable // Lifts /** - * @brief TODO + * @brief Lifts a value into a singular copack, decaying + * + * Unlike `as_pack`, always by value: a copack alternative can never be a reference. * - * @param src TODO - * @return TODO + * @param src Value to lift + * @return A `copack` over the decayed type of `src`, holding it */ [[nodiscard]] constexpr auto as_copack(auto &&src) // noexcept(detail::_nothrow_copack_lift) -> decltype(auto) @@ -1239,10 +1121,11 @@ constexpr inline bool _nothrow_copack_lift } /** - * @brief TODO + * @brief Lifts arguments into a singular copack of `T`, constructed in place * - * @param src TODO - * @return TODO + * @tparam T The sole alternative + * @param args Arguments to construct the alternative from + * @return `copack` holding the alternative */ template [[nodiscard]] constexpr auto as_copack(::std::in_place_type_t, auto &&...args) // @@ -1254,13 +1137,14 @@ template } /** - * @brief TODO + * @brief Compares two copacks: equal when they hold the same alternative type with equal values * - * @tparam Ts TODO - * @tparam Tx TODO - * @param lh TODO - * @param rh TODO - * @return TODO + * The copacks need not have the same alternative sets: an alternative the other side cannot hold + * compares unequal without being compared - and need not even be equality-comparable. + * + * @param lh Left copack + * @param rh Right copack + * @return Whether the active alternatives are the same type with equal values */ template [[nodiscard]] constexpr bool operator==(copack const &lh, copack const &rh) // @@ -1281,9 +1165,14 @@ template // operator=='s constraints and the two cannot drift apart. /** - * @brief TODO + * @brief Builds the canonical `copack` for any list of types + * + * The user-facing construction alias: flattens nested copacks, deduplicates, and sorts into the + * library's canonical order, resolving to the one valid `copack` - in an API signature the two + * are the same type. Spell `copack_for` rather than `copack`, so that no spelling in your project + * is tied to one compiler's alternative order. * - * @tparam Ts TODO + * @tparam Ts Types to combine - alternatives and copacks of them, in any order, duplicates allowed */ template using copack_for diff --git a/include/fn/expected.hpp b/include/fn/expected.hpp index 03b76ec2..e7514513 100644 --- a/include/fn/expected.hpp +++ b/include/fn/expected.hpp @@ -29,14 +29,29 @@ using ::pfn::unexpect; using ::pfn::unexpect_t; using ::pfn::unexpected; +/** + * @brief Checks if a type is an `fn::expected` (with any sides) + * + * @tparam T Type to check, possibly cv-ref qualified + */ template concept some_expected = detail::_some_expected; +/** + * @brief Checks if a type is an `fn::expected` with a non-void value type + * + * @tparam T Type to check, possibly cv-ref qualified + */ template concept some_expected_non_void = // some_expected // && !::std::is_same_v::value_type>; +/** + * @brief Checks if a type is an `fn::expected` over `void` + * + * @tparam T Type to check, possibly cv-ref qualified + */ template concept some_expected_void = // some_expected // @@ -864,7 +879,23 @@ template struct _expected_base : ::pfn::detail::_expect } // namespace detail -// Primary template - non-void value type +/** + * @brief The fallible carrier: a computation yielding success `T` or error `Err` + * + * A strict superset of `std::expected` as specified for C++26, provided here by `pfn::expected`: + * construction, assignment, observers and comparisons are the standard's, and a valid program + * switching from `pfn` to `fn` changes neither compilation nor behaviour. On top of the standard + * contract come the extensions: a `copack` error side enrols the carrier in graded error-set + * unioning, and a `copack` value side in the same arithmetic on values; the `apply` family + * eliminates over both states; `copack_error` and `copack_value` lift a plain side into its + * singular copack; `operator&` conjoins and `operator|` disjoins carriers. An error side + * `copack<>` makes the carrier infallible - the identity `expected` - and a `pack` value spreads + * into callbacks as separate arguments. This primary template serves a non-void `T`; the + * specialization over `void` mirrors it. + * + * @tparam T Value type; `void` selects the specialization + * @tparam Err Error type; a `copack` makes the carrier graded + */ template class expected : private detail::_expected_base { using _base = detail::_expected_base; @@ -1087,6 +1118,17 @@ template class expected : private detail::_expected_b // unpacked as fn::apply would hand it over, keyed (apply_type) by the constructor tag naming // the state - ::std::in_place for the value, ::fn::unexpect for the error. Bodies delegate to // _expected_base static helpers. + /** + * @brief Eliminates over both states: the active side routes into the callable + * + * The value arm receives the value as `fn::apply` hands it over - a `pack` or tuple-like value + * by elements - and the error arm receives the error likewise; the arms must yield one result + * type. Over an uninhabited side the other arm alone is exhaustive. + * + * @param f Callable with arms for both states; `fn::overload` fuses them + * @param args Additional arguments, appended after the content + * @return The callable's result + */ template [[nodiscard]] constexpr auto apply(F &&f, Args &&...args) & // noexcept(noexcept(_base::_apply(*this, FWD(f), FWD(args)...))) // extension @@ -1116,6 +1158,14 @@ template class expected : private detail::_expected_b return _base::_apply(::std::move(*this), FWD(f), FWD(args)...); } + /** + * @brief Eliminates over both states, converting the result to `Ret` + * + * @tparam Ret Type the results convert to + * @param f Callable with arms for both states; `fn::overload` fuses them + * @param args Additional arguments, appended after the content + * @return The callable's result, converted to `Ret` + */ template [[nodiscard]] constexpr auto apply_r(F &&f, Args &&...args) & // noexcept(noexcept(_base::template _apply_r(*this, FWD(f), FWD(args)...))) // extension @@ -1145,6 +1195,17 @@ template class expected : private detail::_expected_b return _base::template _apply_r(::std::move(*this), FWD(f), FWD(args)...); } + /** + * @brief Eliminates over both states, keyed by the constructor tag naming the state + * + * The value arm receives `std::in_place` followed by the value's content - `std::in_place` + * alone where the value type is `void` - and the error arm receives `fn::unexpect` followed by + * the error. + * + * @param f Callable with arms for both tagged states + * @param args Additional arguments, appended after the content + * @return The callable's result + */ template [[nodiscard]] constexpr auto apply_type(F &&f, Args &&...args) & // noexcept(noexcept(_base::_apply_type(*this, FWD(f), FWD(args)...))) // extension @@ -1174,6 +1235,15 @@ template class expected : private detail::_expected_b return _base::_apply_type(::std::move(*this), FWD(f), FWD(args)...); } + /** + * @brief Eliminates over both states, keyed by the constructor tag, converting the result to + * `Ret` + * + * @tparam Ret Type the results convert to + * @param f Callable with arms for both tagged states + * @param args Additional arguments, appended after the content + * @return The callable's result, converted to `Ret` + */ template [[nodiscard]] constexpr auto apply_type_r(F &&f, Args &&...args) & // noexcept(noexcept(_base::template _apply_type_r(*this, FWD(f), FWD(args)...))) // extension @@ -1204,6 +1274,18 @@ template class expected : private detail::_expected_b } // Monadic operations. Bodies delegate to _expected_base static helpers, which perform copack-widening. + /** + * @brief Binds the value through the callable, which returns an `expected` + * + * As the standard member, extended by grading: a plain error side admits a callback returning + * the identical error type, or its singular lift `copack` - the opt-in to the graded world - + * while a graded (copack) error side unions the callback's error set into its own. A + * copack-valued operand dispatches per alternative, exhaustively, heterogeneous branch values + * joining into a normalized copack. The one bind that widens an error grade. + * + * @param f Callable applied on the value, returning an `expected` + * @return The callback's `expected`, its error side widened by the operand's grade + */ template constexpr auto and_then(F &&f) & // noexcept(noexcept(_base::_and_then(*this, FWD(f)))) // extension @@ -1233,6 +1315,19 @@ template class expected : private detail::_expected_b return _base::_and_then(::std::move(*this), FWD(f)); } + /** + * @brief Binds the error through the callable, which returns an `expected` + * + * The recovery bind: a successful operand passes through, and the callback maps the error - per + * alternative when graded, exhaustively - into a new `expected`. The value sides join under the + * grading rules, a plain side admitting its singular lift `copack`; an error alternative + * handled by a branch leaves the grade unless re-returned, and on a plain error side the + * callback's error type replaces the operand's. On the identity `expected` the operation is + * vacuous: nothing is asked of the handler, not even that it be callable. + * + * @param f Callable applied on the error, returning an `expected` + * @return The recovery's `expected`, its value side joined with the operand's + */ template constexpr auto or_else(F &&f) & // noexcept(noexcept(_base::_or_else(*this, FWD(f)))) // extension @@ -1262,6 +1357,17 @@ template class expected : private detail::_expected_b return _base::_or_else(::std::move(*this), FWD(f)); } + /** + * @brief Maps the value through the callable, staying inside the carrier + * + * As the standard member, extended over the algebra: a `pack` value spreads into the callable + * by elements, and a copack-valued operand dispatches per alternative - heterogeneous branch + * results joining into a normalized copack. Over an uninhabited value side the mapping is the + * identity, and the callback is neither invoked nor instantiated. + * + * @param f Callable applied on the value + * @return An `expected` holding the callable's result, with the same error side + */ template constexpr auto transform(F &&f) & // noexcept(noexcept(_base::_transform(*this, FWD(f)))) // extension @@ -1291,6 +1397,17 @@ template class expected : private detail::_expected_b return _base::_transform(::std::move(*this), FWD(f)); } + /** + * @brief Maps the error through the callable, staying inside the carrier + * + * As the standard member, extended by grading: over a graded (copack) error side the matching + * is exhaustive, and the branches may collapse diverse alternatives into one type - the grade + * narrows to its singular copack - or into a narrower copack. Over the uninhabited `copack<>` + * error side the mapping is the identity, and the callback is neither invoked nor instantiated. + * + * @param f Callable applied on the error + * @return An `expected` with the same value side and the mapped error side + */ template constexpr auto transform_error(F &&f) & // noexcept(noexcept(_base::_transform_error(*this, FWD(f)))) // extension @@ -1322,6 +1439,15 @@ template class expected : private detail::_expected_b // Convert to graded monad. A lifting overload wraps one side in a copack and relocates the other // untouched, so it weighs both; the ones whose side already is a copack only return *this. + /** + * @brief Lifts the error side into its singular copack: `expected` becomes + * `expected>` + * + * The explicit entry into the graded world; an already-graded error side returns `*this` + * unchanged. + * + * @return The graded `expected`, relocating both sides + */ constexpr auto copack_error() const & noexcept(::std::is_nothrow_constructible_v && ::std::is_nothrow_constructible_v, error_type const &> @@ -1368,6 +1494,15 @@ template class expected : private detail::_expected_b return ::std::move(*this); } + /** + * @brief Lifts the value side into its singular copack: `expected` becomes + * `expected, E>` + * + * The value-side twin of `copack_error`; an already-copack value side returns `*this` + * unchanged. + * + * @return The graded `expected`, relocating both sides + */ constexpr auto copack_value() const & noexcept(::std::is_nothrow_constructible_v, value_type const &> && ::std::is_nothrow_move_constructible_v> @@ -1425,6 +1560,15 @@ template class expected : private detail::_expected_b } }; +/** + * @brief The `expected` specialization over `void`: success carries no value + * + * As the primary, with the value side the unit: success-path callbacks are invoked with no + * arguments, the `apply` family's value arm receives the trailing arguments alone, and a + * conjunction elides the void side from the value product. + * + * @tparam Err Error type; a `copack` makes the carrier graded + */ template class expected : private detail::_expected_base { using _base = detail::_expected_base; @@ -1890,12 +2034,24 @@ constexpr bool operator==(expected const &x, T2 const &v) // return *x == v; } -// Lifts for copack transformation functions +/** + * @brief Free-function form of the member `copack_value` lift + * + * @param src The `expected` to lift + * @return `src.copack_value()` + */ [[nodiscard]] constexpr auto copack_value(some_expected_non_void auto &&src) noexcept(noexcept(FWD(src).copack_value())) -> decltype(auto) { return FWD(src).copack_value(); } + +/** + * @brief Free-function form of the member `copack_error` lift + * + * @param src The `expected` to lift + * @return `src.copack_error()` + */ [[nodiscard]] constexpr auto copack_error(some_expected auto &&src) noexcept(noexcept(FWD(src).copack_error())) -> decltype(auto) { @@ -1945,6 +2101,21 @@ template struct _expected_efn final { }; } // namespace detail +/** + * @brief The conjunction of fallible carriers: values multiply into a `pack`, errors sum into a + * `copack` + * + * `a & b` succeeds only if both operands succeed, the values folding into one `pack` - a `void` + * side elides, and a copack value distributes into a copack of packs - while at runtime the error + * side holds the leftmost failing operand's error. Two identical error types stay as they are; + * any other pair sums into their normalized `copack_for`, grading not required of the operands. + * Both operands are fully constructed before the operator runs: an error-selection rule, not + * short-circuiting. An identity-cluster operand contributes its value and no error term. + * + * @param lh Left operand + * @param rh Right operand + * @return An `expected` of the folded value product and the summed error side + */ // When any of the sides is expected, we do not produce expected, ...> // Instead just elide void and carry non-void (or elide both voids if that's what we get) template @@ -2263,6 +2434,21 @@ constexpr inline bool _nothrow_disj_error } // namespace detail +/** + * @brief The disjunction of fallible carriers: values sum into a `copack`, errors multiply into a + * `pack` + * + * `a | b` fails only if both operands fail: the leftmost engaged operand's value wins, injected + * into the sum of the value types - a same-type pair stays bare, and a `void` side enters a + * genuine sum as `pack<>` - while the error side is the product of both errors, present only when + * every operand failed, all evidence kept positionally. Both operands are fully constructed + * before the operator runs: a value-selection rule, not a lazy fallback. An identity-cluster + * operand makes the disjunction total, collapsing the result into `just` or `choice`. + * + * @param lh Left operand + * @param rh Right operand + * @return An `expected` of the summed value side and the error product + */ // The disjunction: the value channel is the sum of the value types - a same-type pair stays bare, // as the conjunction's same-error sum does - and the error channel is the product of both errors, // present only when every operand failed, all evidence kept positionally. The leftmost engaged diff --git a/include/fn/fail.hpp b/include/fn/fail.hpp index 7908eb2b..5a87c6a2 100644 --- a/include/fn/fail.hpp +++ b/include/fn/fail.hpp @@ -19,12 +19,10 @@ namespace fn { inline namespace LIBFN_VERSION { /** - * @brief TODO + * @brief Checks if the monadic type can be used with the `fail` operation * - * @tparam Fn TODO - * @tparam V TODO - * @param fn TODO - * @param v TODO + * @tparam Fn The function to map the value into the error, or to observe it on `optional` + * @tparam V The monadic type */ template concept applicable_fail // @@ -41,9 +39,24 @@ concept applicable_fail // }); /** - * @brief TODO + * @brief Intercept the success value and force a transition to the failure state + * + * The dual of `recover`. On `expected` the callback maps the value into the operand's existing + * error type - `fail` never widens a graded error set - and an operand already holding an error + * carries it over. On `optional` the callback observes the value, and must return `void`; the + * result is empty. Rejected on the identity carriers, which have no failure state to enter; over + * an uninhabited value side the operand passes through and the callback is neither invoked nor + * instantiated. + * + * Use through the `fn::fail` nielbloid. */ constexpr inline struct fail_t final { + /** + * @brief Intercept the success value and force a transition to the failure state + * @param fn On `expected`, the function to map the value into the existing error type; on + * `optional`, the function to observe the value, returning `void` + * @return A functor that will fail the monadic type + */ [[nodiscard]] constexpr auto operator()(auto &&fn) const noexcept(noexcept(functor{FWD(fn)})) -> functor // { @@ -53,16 +66,13 @@ constexpr inline struct fail_t final { struct apply; } fail = {}; -/** - * @brief TODO - */ struct fail_t::apply final { /** - * @brief TODO + * @brief Fails the operand: a value maps into an error, an existing error carries over * - * @param v TODO - * @param fn TODO - * @return TODO + * @param v The monad + * @param fn The function to map the value into the error + * @return An `expected` of the same type, holding an error */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const // @@ -83,13 +93,6 @@ struct fail_t::apply final { return type{::fn::unexpect, FWD(v).error()}; } - /** - * @brief TODO - * - * @param v TODO - * @param fn TODO - * @return TODO - */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const // noexcept( @@ -107,11 +110,11 @@ struct fail_t::apply final { } /** - * @brief TODO + * @brief Fails the operand: the value is observed, the result is empty * - * @param v TODO - * @param fn TODO - * @return TODO + * @param v The optional + * @param fn The function to observe the value; must return `void` + * @return An `optional` of the same type, empty */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const // diff --git a/include/fn/filter.hpp b/include/fn/filter.hpp index 02ba6337..f1544663 100644 --- a/include/fn/filter.hpp +++ b/include/fn/filter.hpp @@ -56,7 +56,8 @@ constexpr inline struct filter_t final { /** * @brief Filter the value of the monadic type using a predicate and an error handler * @param pred The predicate to filter the value, takes the value by const reference and returns bool - * @param on_err The error handler, takes the value by const reference and returns the error type + * @param on_err The error handler, returning the error type; consumes the value in the operand's + * value category * @return A functor that will filter the value of the monadic type */ [[nodiscard]] constexpr auto operator()(auto &&pred, auto &&on_err) const @@ -80,17 +81,14 @@ constexpr inline struct filter_t final { struct apply; } filter = {}; -/** - * @brief TODO - */ struct filter_t::apply final { /** - * @brief TODO + * @brief Filters the operand: a value failing the predicate is replaced by the handler's error * - * @param v TODO - * @param pred TODO - * @param on_err TODO - * @return TODO + * @param v The monad + * @param pred The predicate, applied on the value as const + * @param on_err The error handler, consuming the value where the predicate answered false + * @return An `expected` of the same type */ template [[nodiscard]] constexpr auto operator()(V &&v, Pred &&pred, OnErr &&on_err) const // @@ -114,14 +112,6 @@ struct filter_t::apply final { return FWD(v); } - /** - * @brief TODO - * - * @param v TODO - * @param pred TODO - * @param on_err TODO - * @return TODO - */ template [[nodiscard]] constexpr auto operator()(V &&v, Pred &&pred, OnErr &&on_err) const // noexcept( @@ -141,11 +131,11 @@ struct filter_t::apply final { } /** - * @brief TODO + * @brief Filters the operand: a value failing the predicate is replaced by the empty state * - * @param v TODO - * @param pred TODO - * @return TODO + * @param v The optional + * @param pred The predicate, applied on the value as const + * @return An `optional` of the same type */ template [[nodiscard]] constexpr auto operator()(V &&v, Pred &&pred) const // diff --git a/include/fn/functional.hpp b/include/fn/functional.hpp index 38e235b3..4f4fdd56 100644 --- a/include/fn/functional.hpp +++ b/include/fn/functional.hpp @@ -14,32 +14,105 @@ namespace fn { inline namespace LIBFN_VERSION { -// apply_result and apply_result_t +/** + * @brief The result type of `fn::apply` over the arguments + * + * Yields `void` where the application is not viable, as well as for a viable application returning + * `void` - pair with `is_applicable` to tell the two apart. + * + * @tparam Fn Callable to apply + * @tparam Args Arguments as `fn::apply` would take them, including `pack` and `copack` operands + */ template struct apply_result : detail::_apply_result {}; + +/** + * @brief Alias for `apply_result::type` + */ template using apply_result_t = typename apply_result::type; -// is_applicable and is_applicable_v +/** + * @brief Checks if `fn::apply` of `Fn` over the arguments is viable + * + * The multidispatch twin of `std::is_invocable`: a `pack` operand counts by its elements, and a + * `copack` operand counts when every alternative can be dispatched. A set of alternatives which + * disagree on the result type is rejected by a `static_assert` inside the dispatch, not by this + * trait answering false. + * + * @tparam Fn Callable to apply + * @tparam Args Arguments as `fn::apply` would take them + */ template struct is_applicable : detail::_is_applicable {}; + +/** + * @brief Alias for `is_applicable::value` + */ template constexpr inline bool is_applicable_v = is_applicable::value; -// is_applicable_r and is_applicable_r_v +/** + * @brief Checks if `fn::apply_r` of `Fn` over the arguments is viable + * + * As `is_applicable`, except every branch result needs only to be acceptable as `Ret` rather than + * converge on one deduced type. + * + * @tparam Ret Type the results convert to + * @tparam Fn Callable to apply + * @tparam Args Arguments as `fn::apply_r` would take them + */ template struct is_applicable_r : detail::_is_applicable_r {}; + +/** + * @brief Alias for `is_applicable_r::value` + */ template constexpr inline bool is_applicable_r_v = is_applicable_r::value; -// is_nothrow_applicable and is_nothrow_applicable_v +/** + * @brief Checks if `fn::apply` of `Fn` over the arguments is viable and cannot throw + * + * Composes through the dispatch: for a `copack` operand every alternative's call must be nothrow, + * since which one runs is a run-time fact. Answers false where the call is not viable at all. + * + * @tparam Fn Callable to apply + * @tparam Args Arguments as `fn::apply` would take them + */ template struct is_nothrow_applicable : detail::_is_nothrow_applicable {}; + +/** + * @brief Alias for `is_nothrow_applicable::value` + */ template constexpr inline bool is_nothrow_applicable_v = is_nothrow_applicable::value; -// is_nothrow_applicable_r and is_nothrow_applicable_r_v +/** + * @brief Checks if `fn::apply_r` of `Fn` over the arguments is viable and cannot throw + * + * @tparam Ret Type the results convert to + * @tparam Fn Callable to apply + * @tparam Args Arguments as `fn::apply_r` would take them + */ template struct is_nothrow_applicable_r : detail::_is_nothrow_applicable_r {}; + +/** + * @brief Alias for `is_nothrow_applicable_r::value` + */ template constexpr inline bool is_nothrow_applicable_r_v = is_nothrow_applicable_r::value; -// apply +/** + * @brief The multidispatch entry point: unpacks products, dispatches over alternatives, invokes `fn` + * + * A `pack` operand spreads into the call by elements; a lone tuple-like argument has `std::apply`'s + * meaning; a `copack` operand dispatches on its active alternative, itself unpacked one level when + * tuple-like; several pack/copack operands first fold into one, distributing alternatives over + * products. The arm is selected by ordinary C++ overload resolution, and the dispatch is + * exhaustive: an alternative without a viable arm makes the whole call not applicable. + * + * @param fn Callable; `fn::overload` fuses per-alternative arms into one + * @param args Any mix of scalars, `pack`s and `copack`s, in call order + * @return Result of invoking `fn` on the selected call shape + */ template requires is_applicable_v constexpr auto apply(Fn &&fn, Args &&...args) noexcept(is_nothrow_applicable_v) @@ -48,7 +121,18 @@ constexpr auto apply(Fn &&fn, Args &&...args) noexcept(is_nothrow_applicable_v requires is_applicable_r_v constexpr auto apply_r(Fn &&fn, Args &&...args) noexcept(is_nothrow_applicable_r_v) -> Ret @@ -56,15 +140,47 @@ constexpr auto apply_r(Fn &&fn, Args &&...args) noexcept(is_nothrow_applicable_r return detail::_apply_r(FWD(fn), FWD(args)...); } -// applicable and regular_applicable +/** + * @brief Checks if `fn::apply` of `Fn` over `Args` is viable - the concept form of `is_applicable` + * + * @tparam Fn Callable to apply + * @tparam Args Arguments as `fn::apply` would take them + */ template concept applicable = is_applicable_v; +/** + * @brief As `applicable`, adding the semantic promise of equality preservation - mirroring + * `std::regular_invocable` against `std::invocable` + * + * @tparam Fn Callable to apply + * @tparam Args Arguments as `fn::apply` would take them + */ template concept regular_applicable = applicable; +/** + * @brief Checks if `Fn` can be applied to every alternative of the typelist `T` + * + * The exhaustiveness question `copack` and `choice` dispatch asks before selecting an arm: each + * alternative is tried in `T`'s own cv-ref qualification, with the trailing `Args` following the + * alternative's content. + * + * @tparam Fn Callable to apply + * @tparam T The typelist - a `copack` or `choice`, cv-ref qualified + * @tparam Args Trailing arguments, appended after the alternative's content + */ template concept typelist_applicable = detail::_typelist_applicable; + +/** + * @brief As `typelist_applicable`, with every branch result converting to `Ret` + * + * @tparam Ret Type the results convert to + * @tparam Fn Callable to apply + * @tparam T The typelist - a `copack` or `choice`, cv-ref qualified + * @tparam Args Trailing arguments, appended after the alternative's content + */ template concept typelist_applicable_r = detail::_typelist_applicable_r; diff --git a/include/fn/functor.hpp b/include/fn/functor.hpp index 14ccef24..de4feeca 100644 --- a/include/fn/functor.hpp +++ b/include/fn/functor.hpp @@ -20,10 +20,15 @@ namespace fn { inline namespace LIBFN_VERSION { /** - * @brief TODO + * @brief A pipeline step waiting for a carrier: the verb and its arguments as one value * - * @tparam Functor TODO - * @tparam Args TODO + * What a verb call such as `fn::and_then(f)` returns - a description of the operation, executed + * only when a carrier is piped into it with `operator|`. The arguments are stored as `as_value_t` + * prescribes: rvalues by value, non-empty lvalues - an immovable callable among them - by + * reference. + * + * @tparam Functor The verb, such as `fn::and_then_t` + * @tparam Args The verb's arguments as deduced, typically the callback */ template struct functor final { using functor_type = Functor; @@ -36,11 +41,11 @@ template struct functor final { && ::std::is_default_constructible_v && ::std::is_default_constructible_v); /** - * @brief TODO + * @brief Feeds a carrier into the pipeline step * - * @param v TODO - * @param self TODO - * @return TODO + * @param v The carrier, in any value category + * @param self The step - this `functor` + * @return Whatever the verb's `apply` returns for this carrier and the stored arguments */ // The pipeline propagates what the operation itself promises: the verb's `apply` knows, and // `_swap_invoke` carries that answer up. Promising `noexcept` here regardless would turn an diff --git a/include/fn/inspect.hpp b/include/fn/inspect.hpp index eca9b624..c11cbe19 100644 --- a/include/fn/inspect.hpp +++ b/include/fn/inspect.hpp @@ -22,10 +22,10 @@ namespace fn { inline namespace LIBFN_VERSION { /** - * @brief TODO + * @brief Checks if the monadic type can be used with the `inspect` operation * - * @tparam Fn TODO - * @tparam V TODO + * @tparam Fn The function to observe the value + * @tparam V The monadic type */ template concept applicable_inspect // @@ -44,14 +44,20 @@ concept applicable_inspect // }); /** - * @brief TODO + * @brief Observe the value for side-effects, passing the operand through unchanged + * + * The callback receives the value as const and must return `void`: observation can neither mutate + * the operand nor replace it. Where the carrier can be empty or hold an error, the callback runs + * only when a value is present; on `choice` and `just` it always runs; over an uninhabited value + * side it is neither invoked nor instantiated. + * + * Use through the `fn::inspect` nielbloid. */ constexpr inline struct inspect_t final { /** - * @brief TODO - * - * @param fn TODO - * @return TODO + * @brief Observe the value for side-effects, passing the operand through unchanged + * @param fn The function to observe the value; invoked with no arguments where the value is `void` + * @return A functor that will execute the function on the value */ [[nodiscard]] constexpr auto operator()(auto &&fn) const noexcept(noexcept(functor{FWD(fn)})) -> functor @@ -64,11 +70,11 @@ constexpr inline struct inspect_t final { struct inspect_t::apply final { /** - * @brief TODO + * @brief Observes the value for side-effects, when one is present * - * @param v TODO - * @param fn TODO - * @return TODO + * @param v The monad + * @param fn The function to observe the value + * @return The operand, forwarded unchanged */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const @@ -81,13 +87,6 @@ struct inspect_t::apply final { return FWD(v); } - /** - * @brief TODO - * - * @param v TODO - * @param fn TODO - * @return TODO - */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const noexcept(::fn::is_nothrow_applicable_v) -> V && requires applicable_inspect @@ -98,13 +97,6 @@ struct inspect_t::apply final { return FWD(v); } - /** - * @brief TODO - * - * @param v TODO - * @param fn TODO - * @return TODO - */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const noexcept(::fn::is_nothrow_applicable_v) -> V && @@ -116,13 +108,6 @@ struct inspect_t::apply final { return FWD(v); } - /** - * @brief TODO - * - * @param v TODO - * @param fn TODO - * @return TODO - */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const noexcept(::fn::is_nothrow_applicable_v) -> V && @@ -132,13 +117,6 @@ struct inspect_t::apply final { return FWD(v); } - /** - * @brief TODO - * - * @param v TODO - * @param fn TODO - * @return TODO - */ template requires(not ::std::is_void_v::value_type>) [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const @@ -149,13 +127,6 @@ struct inspect_t::apply final { return FWD(v); } - /** - * @brief TODO - * - * @param v TODO - * @param fn TODO - * @return TODO - */ template requires ::std::is_void_v::value_type> [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const noexcept(::fn::is_nothrow_applicable_v) -> V && diff --git a/include/fn/inspect_error.hpp b/include/fn/inspect_error.hpp index a0a96ca8..3c0aaff4 100644 --- a/include/fn/inspect_error.hpp +++ b/include/fn/inspect_error.hpp @@ -18,10 +18,10 @@ namespace fn { inline namespace LIBFN_VERSION { /** - * @brief TODO + * @brief Checks if the monadic type can be used with the `inspect_error` operation * - * @tparam Fn TODO - * @tparam V TODO + * @tparam Fn The function to observe the error + * @tparam V The monadic type */ template concept applicable_inspect_error // @@ -32,14 +32,20 @@ concept applicable_inspect_error // }); /** - * @brief TODO + * @brief Observe the error for side-effects, passing the operand through unchanged + * + * The callback receives the error as const and must return `void`; on `optional` it is invoked + * with no arguments - the empty state carries no error value. Rejected on `choice` and `just`, + * which have no error side; on the identity `expected` it is vacuous - the operand passes through + * and the callback is never instantiated. + * + * Use through the `fn::inspect_error` nielbloid. */ constexpr inline struct inspect_error_t final { /** - * @brief TODO - * - * @param fn TODO - * @return TODO + * @brief Observe the error for side-effects, passing the operand through unchanged + * @param fn The function to observe the error; invoked with no arguments on `optional` + * @return A functor that will execute the function on the error */ [[nodiscard]] constexpr auto operator()(auto &&fn) const noexcept(noexcept(functor{FWD(fn)})) -> functor // @@ -50,16 +56,13 @@ constexpr inline struct inspect_error_t final { struct apply; } inspect_error = {}; -/** - * @brief TODO - */ struct inspect_error_t::apply final { /** - * @brief TODO + * @brief Observes the error for side-effects, when one is present * - * @param v TODO - * @param fn TODO - * @return TODO + * @param v The monad + * @param fn The function to observe the error + * @return The operand, forwarded unchanged */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const @@ -72,12 +75,6 @@ struct inspect_error_t::apply final { return FWD(v); } - /** - * @brief TODO - * - * @param v TODO - * @return TODO - */ // An identity expected's error side is uninhabited - there is nothing to observe, the operand // passes through and the callback is never instantiated template @@ -87,13 +84,6 @@ struct inspect_error_t::apply final { return FWD(v); } - /** - * @brief TODO - * - * @param v TODO - * @param fn TODO - * @return TODO - */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const noexcept(::fn::is_nothrow_applicable_v) -> V && requires applicable_inspect_error diff --git a/include/fn/optional.hpp b/include/fn/optional.hpp index b470bff7..4331da5f 100644 --- a/include/fn/optional.hpp +++ b/include/fn/optional.hpp @@ -26,6 +26,11 @@ namespace fn { inline namespace LIBFN_VERSION { +/** + * @brief Checks if a type is an `fn::optional` (with any value type) + * + * @tparam T Type to check, possibly cv-ref qualified + */ template concept some_optional = detail::_some_optional; @@ -494,6 +499,20 @@ template struct _optional_base : ::pfn::detail::_optional_base` is served by the + * specialization. + * + * @tparam T Value type; an lvalue reference selects the specialization + */ template class optional : private detail::_optional_base { // NOSONAR cpp:S3624 base manages storage static_assert(::pfn::detail::_is_valid_optional); using _base = detail::_optional_base; @@ -663,6 +682,17 @@ template class optional : private detail::_optional_base { // NO // Elimination over both states, mirroring copack's apply family: the engaged arm takes the value // unpacked as fn::apply would hand it over, the empty arm takes no value (apply) or the nullopt // tag alone (apply_type). Bodies delegate to _optional_base static helpers. + /** + * @brief Eliminates over both states: the engaged arm receives the value, the empty arm nothing + * + * The engaged arm receives the value as `fn::apply` hands it over - a `pack` or tuple-like + * value by elements - and the empty arm is invoked with the trailing arguments alone; the arms + * must yield one result type. + * + * @param f Callable with arms for both states; `fn::overload` fuses them + * @param args Additional arguments, appended after the content + * @return The callable's result + */ template [[nodiscard]] constexpr auto apply(F &&f, Args &&...args) & // noexcept(noexcept(_base::_apply(*this, FWD(f), FWD(args)...))) // extension @@ -692,6 +722,14 @@ template class optional : private detail::_optional_base { // NO return _base::_apply(::std::move(*this), FWD(f), FWD(args)...); } + /** + * @brief Eliminates over both states, converting the result to `Ret` + * + * @tparam Ret Type the results convert to + * @param f Callable with arms for both states; `fn::overload` fuses them + * @param args Additional arguments, appended after the content + * @return The callable's result, converted to `Ret` + */ template [[nodiscard]] constexpr auto apply_r(F &&f, Args &&...args) & // noexcept(noexcept(_base::template _apply_r(*this, FWD(f), FWD(args)...))) // extension @@ -721,6 +759,16 @@ template class optional : private detail::_optional_base { // NO return _base::template _apply_r(::std::move(*this), FWD(f), FWD(args)...); } + /** + * @brief Eliminates over both states, keyed by the tag naming the state + * + * The engaged arm receives `std::in_place` followed by the value's content, and the empty arm + * receives `std::nullopt`. + * + * @param f Callable with arms for both tagged states + * @param args Additional arguments, appended after the content + * @return The callable's result + */ template [[nodiscard]] constexpr auto apply_type(F &&f, Args &&...args) & // noexcept(noexcept(_base::_apply_type(*this, FWD(f), FWD(args)...))) // extension @@ -750,6 +798,14 @@ template class optional : private detail::_optional_base { // NO return _base::_apply_type(::std::move(*this), FWD(f), FWD(args)...); } + /** + * @brief Eliminates over both states, keyed by the tag, converting the result to `Ret` + * + * @tparam Ret Type the results convert to + * @param f Callable with arms for both tagged states + * @param args Additional arguments, appended after the content + * @return The callable's result, converted to `Ret` + */ template [[nodiscard]] constexpr auto apply_type_r(F &&f, Args &&...args) & // noexcept(noexcept(_base::template _apply_type_r(*this, FWD(f), FWD(args)...))) // extension @@ -780,6 +836,17 @@ template class optional : private detail::_optional_base { // NO } // Monadic operations. Bodies delegate to _optional_base static helpers, which perform copack-widening. + /** + * @brief Binds the value through the callable, which returns an `optional` + * + * As the standard member, extended over the algebra: a copack-valued operand dispatches per + * alternative, exhaustively, heterogeneous branch values joining into a normalized copack. An + * empty operand passes through, and over an uninhabited value side the callback is neither + * invoked nor instantiated. + * + * @param f Callable applied on the value, returning an `optional` + * @return The callback's `optional`; heterogeneous branch values join into a copack + */ template constexpr auto and_then(F &&f) & // noexcept(noexcept(_base::_and_then(*this, FWD(f)))) // extension @@ -812,6 +879,16 @@ template class optional : private detail::_optional_base { // NO // or_else has only const& and && overloads (mirroring pfn::optional): an extra & or const&& // overload would silently change which reference category an engaged value is copied through // in a program switched over from pfn::optional. + /** + * @brief Binds the empty state through the callable, which returns an `optional` + * + * The recovery bind: an engaged operand passes through, and the callback - invoked with no + * arguments, the empty state carrying no value - names the result. The value sides join under + * the grading rules, a plain side admitting its singular lift `copack`. + * + * @param f Callable invoked with no arguments, returning an `optional` + * @return The recovery's `optional`, its value side joined with the operand's + */ template constexpr auto or_else(F &&f) const & // noexcept(noexcept(_base::_or_else(*this, FWD(f)))) // extension @@ -827,6 +904,17 @@ template class optional : private detail::_optional_base { // NO return _base::_or_else(::std::move(*this), FWD(f)); } + /** + * @brief Maps the value through the callable, staying inside the carrier + * + * As the standard member, extended over the algebra: a `pack` value spreads into the callable + * by elements, and a copack-valued operand dispatches per alternative - heterogeneous branch + * results joining into a normalized copack. Over an uninhabited value side the mapping is the + * identity, and the callback is neither invoked nor instantiated. + * + * @param f Callable applied on the value + * @return An `optional` holding the callable's result + */ template constexpr auto transform(F &&f) & // noexcept(noexcept(_base::_transform(*this, FWD(f)))) // extension @@ -858,6 +946,15 @@ template class optional : private detail::_optional_base { // NO // Convert to graded monad. The lifting overloads wrap the value in a copack and that copack in the // result, so they weigh both; the ones whose value type already is a copack only return *this. + /** + * @brief Lifts the value side into its singular copack: `optional` becomes + * `optional>` + * + * The explicit entry into the graded world; an already-copack value side returns `*this` + * unchanged. + * + * @return The graded `optional`, relocating the value + */ constexpr auto copack_value() const & noexcept(::std::is_nothrow_constructible_v, value_type const &> && ::std::is_nothrow_move_constructible_v>) // extension @@ -915,6 +1012,15 @@ template class optional : private detail::_optional_base { // NO template optional(T) -> optional; +/** + * @brief The `optional` specialization over an lvalue reference: a rebindable, non-owning view + * + * As `std::optional` is specified for C++26 - the one carrier that holds a raw reference. + * Lifetime responsibility for the referent stays with the caller. The extensions mirror + * `optional`'s, the callable always receiving a plain `T&`. + * + * @tparam T Referent type + */ // Partial specialization for lvalue reference types, mirroring pfn::optional. The monadic // operations delegate to the same fn::detail::_optional_base statics as optional's, which // the underlying pfn reference base makes shallow: the callable always receives plain T&, and @@ -1310,6 +1416,18 @@ struct _optional_efn final { }; } // namespace detail +/** + * @brief The conjunction of optionals: values multiply into a `pack`, empty is the one failure + * + * `a & b` is engaged only if both operands are, the values folding into one `pack` - a copack + * value distributing into a copack of packs - and empty otherwise: `optional`'s unit error needs + * no summing. Both operands are fully constructed before the operator runs. An identity-cluster + * operand contributes its value and can never be the empty side. + * + * @param lh Left operand + * @param rh Right operand + * @return An `optional` of the folded value product + */ template [[nodiscard]] constexpr auto operator&(Lh &&lh, Rh &&rh) // noexcept(noexcept(::fn::detail::_join(FWD(lh), FWD(rh), detail::_optional_efn{}))) @@ -1429,6 +1547,18 @@ template return ::std::remove_cvref_t{FWD(lh)}; } +/** + * @brief The disjunction of optionals: values sum into a `copack`, empty only when both are + * + * `a | b` holds the leftmost engaged operand's value, injected into the sum of the value types - + * a same-type pair stays bare. The unit errors vanish in the error product, so the result is + * empty exactly when both operands are. Both operands are fully constructed before the operator + * runs: a value-selection rule, not a lazy fallback. + * + * @param lh Left operand + * @param rh Right operand + * @return An `optional` of the summed value side + */ // The disjunction: the value channel is the sum of the value types - a same-type pair stays bare - // and the unit errors vanish in the product, so the result is empty exactly when both operands // are. The leftmost engaged operand wins and injects by type. diff --git a/include/fn/or_else.hpp b/include/fn/or_else.hpp index ccd8ffdd..6d4f88f2 100644 --- a/include/fn/or_else.hpp +++ b/include/fn/or_else.hpp @@ -87,10 +87,10 @@ struct _nothrow_or_else_to_optional { }; } // namespace detail /** - * @brief TODO + * @brief Checks if the monadic type can be used with the `or_else` operation * - * @tparam Fn TODO - * @tparam V TODO + * @tparam Fn The function to execute on the dead state + * @tparam V The monadic type */ template concept applicable_or_else // @@ -147,14 +147,24 @@ concept applicable_or_else_across // })); /** - * @brief TODO + * @brief Sequence a recovery computation on the failure path, joining its values + * + * The callback witnesses the dead state - `expected`'s error, per alternative when graded, or + * `optional`'s empty state, with no arguments - and returns a carrier; a successful operand passes + * through, bypassing it. Recovery branches may return heterogeneous carriers of the same value + * kind: their values join, and an error alternative handled by a branch leaves the grade unless + * re-returned. In the pipeline form the callback's carrier may also bridge across the + * expected/optional pair; on the identity `expected` the operation is vacuous - nothing is asked + * of the handler, not even that it be callable. + * + * Use through the `fn::or_else` nielbloid. */ constexpr inline struct or_else_t final { /** - * @brief TODO - * - * @param fn TODO - * @return TODO + * @brief Sequence a recovery computation on the failure path, joining its values + * @param fn The function to execute on the dead state - the error on `expected`, no arguments + * on `optional` - returning a carrier + * @return A functor that will recover the monadic type */ [[nodiscard]] constexpr auto operator()(auto &&fn) const noexcept(noexcept(functor{FWD(fn)})) -> functor // @@ -165,16 +175,13 @@ constexpr inline struct or_else_t final { struct apply; } or_else = {}; -/** - * @brief TODO - */ struct or_else_t::apply final { /** - * @brief TODO + * @brief Recovers through the carrier's own `or_else` member * - * @param v TODO - * @param fn TODO - * @return TODO + * @param v The monad + * @param fn The function to execute on the dead state + * @return A carrier of the same value kind */ template [[nodiscard]] constexpr auto operator()(V &&v, Fn &&fn) const // @@ -185,13 +192,6 @@ struct or_else_t::apply final { return FWD(v).or_else(FWD(fn)); } - /** - * @brief TODO - * - * @param v TODO - * @param fn TODO - * @return TODO - */ // An identity expected's error side is uninhabited - delegate to the vacuous member, which // accepts any callback and never instantiates it template diff --git a/include/fn/pack.hpp b/include/fn/pack.hpp index fc5ded60..2f981478 100644 --- a/include/fn/pack.hpp +++ b/include/fn/pack.hpp @@ -20,19 +20,26 @@ namespace fn { inline namespace LIBFN_VERSION { /** - * @brief TODO + * @brief Checks if a type is a `pack` (with any elements) * - * @tparam T TODO + * @tparam T Type to check, possibly cv-ref qualified */ template concept some_pack = detail::_some_pack; /** - * @brief TODO + * @brief The product payload: all fields present, strictly flat * - * Some more text here. + * A tuple-like structure supporting the tuple protocol - `get`, `tuple_size`, `tuple_element`, + * structured bindings - and an `append` mechanism. Unlike `std::tuple`, a `pack` is not a valid + * element of a `pack`: appending one splices its fields in rather than nesting it. Elements may + * be lvalue references - `pack` is how the other carriers propagate references - and a const + * pack propagates its const onto reference elements, handing out read-only views of the + * referenced data. `pack<>` is the nullary product, the algebra's unit: a value that exists and + * holds nothing, where the uninhabited `copack<>` is the zero. A structural type when its + * elements are, so a constexpr pack can be used as a template parameter. * - * @tparam Ts TODO + * @tparam Ts Element types; values or lvalue references, never rvalue references */ template struct pack : detail::pack_impl<::std::index_sequence_for, Ts...> { using _impl = detail::pack_impl<::std::index_sequence_for, Ts...>; @@ -63,11 +70,16 @@ template struct pack : detail::pack_impl<::std::index_sequence_ } /** - * @brief appends a thing + * @brief Appends an element of type `T`, constructed in place from the arguments * - * @tparam T TODO - * @param args TODO - * @return TODO + * A `pack` for `T` splices its fields in - packs stay strictly flat - and a `copack` is not a + * valid element at all. The result is a new pack; the existing elements are copied or moved in + * `*this`'s value category, so a const pack with a reference element is not appendable: const + * propagates through the reference, and the new pack's element cannot bind it. + * + * @tparam T Type of the new element + * @param args Arguments to construct the new element from + * @return A new pack, with the new element at the end */ template [[nodiscard]] constexpr auto append(::std::in_place_type_t, auto &&...args) & // @@ -77,13 +89,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return {_impl::template _append(*this, FWD(args)...)}; } - /** - * @brief appends a thing - * - * @tparam T TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto append(::std::in_place_type_t, auto &&...args) const & // noexcept(noexcept(_impl::template _append(::std::declval(), FWD(args)...))) -> append_type @@ -92,13 +97,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return {_impl::template _append(*this, FWD(args)...)}; } - /** - * @brief appends a thing - * - * @tparam T TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto append(::std::in_place_type_t, auto &&...args) && // noexcept(noexcept(_impl::template _append(::std::declval(), FWD(args)...))) -> append_type @@ -107,13 +105,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return {_impl::template _append(::std::move(*this), FWD(args)...)}; } - /** - * @brief appends a thing - * - * @tparam T TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto append(::std::in_place_type_t, auto &&...args) const && // noexcept(noexcept(_impl::template _append(::std::declval(), FWD(args)...))) -> append_type @@ -123,11 +114,13 @@ template struct pack : detail::pack_impl<::std::index_sequence_ } /** - * @brief appends a thing + * @brief Appends a value; a `pack` argument splices its fields in + * + * Packs stay strictly flat: appending a pack appends its fields, never a nested pack. The + * result is a new pack; the existing elements are copied or moved in `*this`'s value category. * - * @tparam Arg TODO - * @param arg TODO - * @return TODO + * @param arg Value to append, or a pack whose fields to append + * @return A new pack, with the addition at the end */ template [[nodiscard]] constexpr auto append(Arg &&arg) & // @@ -138,13 +131,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return {_impl::template _append(*this, FWD(arg))}; } - /** - * @brief appends a thing - * - * @tparam Arg TODO - * @param arg TODO - * @return TODO - */ template [[nodiscard]] constexpr auto append(Arg &&arg) const & // noexcept(noexcept(_impl::template _append(::std::declval(), FWD(arg)))) -> append_type @@ -154,13 +140,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return {_impl::template _append(*this, FWD(arg))}; } - /** - * @brief appends a thing - * - * @tparam Arg TODO - * @param arg TODO - * @return TODO - */ template [[nodiscard]] constexpr auto append(Arg &&arg) && // noexcept(noexcept(_impl::template _append(::std::declval(), FWD(arg)))) -> append_type @@ -170,13 +149,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return {_impl::template _append(::std::move(*this), FWD(arg))}; } - /** - * @brief appends a thing - * - * @tparam Arg TODO - * @param arg TODO - * @return TODO - */ template [[nodiscard]] constexpr auto append(Arg &&arg) const && // noexcept(noexcept(_impl::template _append(::std::declval(), FWD(arg)))) -> append_type @@ -187,12 +159,15 @@ template struct pack : detail::pack_impl<::std::index_sequence_ } /** - * @brief TODO + * @brief Eliminates the pack: the elements spread into the callable as separate arguments + * + * The bridge between the product as stored and the product as an argument list: `pack` + * invokes `f(a, b)`, and `pack<>` invokes `f()`. The elements are handed over in `*this`'s + * cv-qualification and value category, and trailing arguments follow them. * - * @tparam Fn TODO - * @param fn TODO - * @param args TODO - * @return TODO + * @param fn Callable applied on the elements + * @param args Additional arguments, appended after the elements + * @return The callable's result */ template [[nodiscard]] constexpr auto apply(Fn &&fn, auto &&...args) & // @@ -203,14 +178,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return _impl::_apply(*this, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply(Fn &&fn, auto &&...args) const & // noexcept(noexcept(_impl::_apply(::std::declval(), FWD(fn), FWD(args)...))) @@ -220,14 +187,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return _impl::_apply(*this, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply(Fn &&fn, auto &&...args) && // noexcept(noexcept(_impl::_apply(::std::declval(), FWD(fn), FWD(args)...))) @@ -237,14 +196,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return _impl::_apply(::std::move(*this), FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Fn TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply(Fn &&fn, auto &&...args) const && // noexcept(noexcept(_impl::_apply(::std::declval(), FWD(fn), FWD(args)...))) @@ -255,13 +206,12 @@ template struct pack : detail::pack_impl<::std::index_sequence_ } /** - * @brief TODO + * @brief Eliminates the pack, converting the result to `Ret` * - * @tparam Ret TODO - * @tparam Fn TODO - * @param fn TODO - * @param args TODO - * @return TODO + * @tparam Ret Type the result converts to + * @param fn Callable applied on the elements + * @param args Additional arguments, appended after the elements + * @return The callable's result, converted to `Ret` */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, auto &&...args) & // @@ -271,15 +221,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return _impl::template _apply_r(*this, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, auto &&...args) const & // noexcept(noexcept(_impl::template _apply_r(::std::declval(), FWD(fn), FWD(args)...))) -> Ret @@ -288,15 +229,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return _impl::template _apply_r(*this, FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, auto &&...args) && // noexcept(noexcept(_impl::template _apply_r(::std::declval(), FWD(fn), FWD(args)...))) -> Ret @@ -305,15 +237,6 @@ template struct pack : detail::pack_impl<::std::index_sequence_ return _impl::template _apply_r(::std::move(*this), FWD(fn), FWD(args)...); } - /** - * @brief TODO - * - * @tparam Ret TODO - * @tparam Fn TODO - * @param fn TODO - * @param args TODO - * @return TODO - */ template [[nodiscard]] constexpr auto apply_r(Fn &&fn, auto &&...args) const && // noexcept(noexcept(_impl::template _apply_r(::std::declval(), FWD(fn), FWD(args)...))) -> Ret @@ -345,15 +268,21 @@ template <::std::size_t I, some_pack P> // Lifts /** - * @brief TODO - * - * @tparam T TODO - * @tparam Args TODO - * @param src TODO - * @param args TODO - * @return TODO + * @brief Lifts no values into the empty `pack` - the unit + * @return `pack<>` */ [[nodiscard]] constexpr auto as_pack() noexcept -> pack<> { return {}; } + +/** + * @brief Lifts values into a `pack`, deduction preserving each argument's value category + * + * `as_pack(42)` yields `pack`; an lvalue `x` yields `pack` - a reference rather than + * a copy. + * + * @param src First value to lift + * @param args Further values to lift + * @return The new pack + */ // The unused leading pack absorbs explicit template arguments and the constraint rejects them: // this overload is deduction-only (value-category preserving), the overload below serves spelled types template @@ -364,6 +293,20 @@ template { return pack{FWD(src), FWD(args)...}; } + +/** + * @brief Lifts values into a `pack` of exactly the spelled element types + * + * `as_pack(x, d)` converts each argument at the call boundary; every element type must + * be spelled - a partial spelling is not viable - and a reference element is what you ask for, as + * in `as_pack(x)`. + * + * @tparam T First element type, as spelled + * @tparam Args Further element types, as spelled + * @param src First value, converted to `T` + * @param args Further values, converted to `Args...` + * @return The new pack + */ // No element type is deduced: the explicit form names ALL of pack or is not viable // (a partial spelling fails on arity); by-value parameters admit conversion at the call boundary // (narrowing included) while still relocating rvalue arguments @@ -466,11 +409,17 @@ template