Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 65 additions & 200 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ To make an `fn`-level facility available to `fn/detail`, hoist it: the implement

The namespace spelling is derived, not copied: 0.y lines with y ≥ 1 share `v0_<y>` (z bumps are ABI-compatible), the 0.0.z line versions per patch, a SemVer prerelease is appended (`-dev` → `_dev`), and the `_cxx26` twin (selected by defining `LIBFN_CXX26`) keeps `_cxx26` last. `pfn` is mode-less: its layouts never depend on the C++26 type ordering or other language features, so it wraps in `LIBFN_VERSION_BASE` — the plain spelling regardless of mode — and its types stay link-compatible across modes. A second hook (`scripts/check_namespace_wrap.py`) verifies the layer rule: every `namespace fn` opening in `include/` carries `inline namespace LIBFN_VERSION`, every `namespace pfn` opening `inline namespace LIBFN_VERSION_BASE`.

CHANGELOG.md is summarized immediately before a release: the accumulated dated entries collapse into a smaller list describing changes in a compact manner, without dates. The summary also names the commit carrying the last complete detailed list — the one right before the first release candidate — where the full history stays readable.

## Pre-commit

This repository uses [pre-commit](https://pre-commit.com/) to enforce formatting of the C++ source code and perform other checks. The details can be seen in `.pre-commit-config.yaml`. To install git commit hooks, which will run checks on the repository as you commit changes:
Expand Down
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module(
name = "libfn",
version = "0.0.9",
version = "0.1.0-rc1",
)

bazel_dep(name = "rules_cc", version = "0.1.1")
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public:
-> fn::expected<Rational, fn::copack_for<DivByZero, Overflow>>
{
if (d == 0) return fn::unexpected{fn::copack{DivByZero{}}};
// Note, std::gcd precondition is that `|n|` and `|d|` must both be representable.
if (n == std::numeric_limits<long long>::min() || d == std::numeric_limits<long long>::min())
return fn::unexpected{fn::copack{Overflow{}}};

Expand Down Expand Up @@ -117,7 +118,7 @@ The library features demonstrated by the code example above:

* **Monadic sequences** — `operator|` pipes a `expected` (or `optional`) through operations: `and_then` and `transform` act on the value, `or_else`, `recover` and `transform_error` on the error, with `filter`, `inspect`, `fail` and more besides.
* **Graded errors** — each stage fails its own way — a malformed string, a zero denominator, an out-of-range result — and the library folds these into one `copack` whose type it derives for you: here `copack<DivByZero, NotANumber, Overflow>`, never spelled by hand.
* **Composing values** — `operator&` gathers successful operands left to right: two values become a `pack`, a third appends to it. A `pack` is a heterogeneous product — the operands as one value, spread into the next call; for example in `make`, where a `pack<int, int>` returned from `parse` is passed to an overload taking two numbers.
* **Composing values** — `operator&` gathers successful operands left to right: two values become a `pack`, a third appends to it. A `pack` is a heterogeneous product — the operands as one value, spread into the next call; for example in `make`'s `string_view` overload, where a `pack<int, int>` returned from `parse` is passed to an overload taking two numbers.
* **Composing alternatives** — when a side is a `copack` (a co-product — one of several types, indexed by type, not by position like `std::variant`), `&` distributes over it, pairing every alternative with the other operand. Two copacks yield the full cartesian product. The result type is flattened, deduplicated and sorted for you.
* **Multidispatch** — the pack (or copack of packs) flows into the next stage as separate arguments. An `fn::overload` — or any function — dispatches on the runtime alternative by ordinary overload resolution. Dispatch is exhaustive: a missing handler is a compile error.
* **Identity monad** — `expected<T, copack<>>` cannot hold an error (enforced at compile time), a spelling of the identity monad; the example lifts `op` into it as `Op`.
Expand Down Expand Up @@ -157,20 +158,20 @@ A third target, `libfn::fn_cxx26`, is the same headers entered with the [`LIBFN_

With `libfn::fn_cxx26`, a compiler that does not implement `std::type_order` stops at the first libfn header, with an `#error` naming the feature. Mixing the two entry points in one binary stops at the linker, on an undefined reference whose type names differ from the definition's by the `_cxx26` ABI namespace. Both are loud by design: the namespaces are separate so that two layouts cannot merge unnoticed — the invariant [TYPE_ALGEBRA.md](TYPE_ALGEBRA.md) calls one normalization order per program.

Packaging is provided — and exercised by CI — for [conan](conanfile.py), [vcpkg](ports/libfn) (an in-repo port), [Nix](flake.nix) and [Bazel](MODULE.bazel); plain CMake `FetchContent` or `add_subdirectory` works as well. Until the first tagged release, consume a pinned git revision — and read [Backwards compatibility](#backwards-compatibility).
Packaging is provided — and exercised by CI — for [conan](conanfile.py), [vcpkg](ports/libfn) (an in-repo port), [Nix](flake.nix) and [Bazel](MODULE.bazel); plain CMake `FetchContent` or `add_subdirectory` works as well. Consume a tagged release — and read [Backwards compatibility](#backwards-compatibility).

Every packaging route above except Bazel also delivers the compile options the headers require. Under Bazel — and a plain copy of `include/` — these options don't arrive automatically; provide them yourself: C++20 or newer (`--cxxopt=-std=c++20` in Bazel), `-Wno-missing-braces` on clang (`fn::pack` initialization elides braces by design), and with MSVC `/permissive-` plus `_HAS_CXX23`. The authoritative set is the `INTERFACE` options in [cmake/CompilationOptions.cmake](cmake/CompilationOptions.cmake).

## Backwards compatibility

The maintainers aim for compatibility with the proposed changes to the C++ standard library, **rather than with the existing uses** of the code in this repo. In practice, this means that all code in this repo should be considered "under intensive development and unstable" until the standardization of the proposed facilities.
The maintainers aim for compatibility with the proposed changes to the C++ standard library, **rather than with the existing uses** of the code in this repo. A facility proposed in `include/fn` therefore tracks its paper: names and semantics may change when the paper does. Such a change bumps **`y`**, and so arrives only with a deliberate upgrade.

## Versioning and ABI

Releases are numbered `0.y.z` and will stay below `1.0.0` for the foreseeable future. [SemVer](https://semver.org/) treats any `0.y.z` version as unstable — anything may change — so libfn narrows that into a usable contract:

- a bump in **`y`** is a **breaking** change (API and/or ABI);
- a bump in **`z`** is a bug fix or a purely additive extension: the API and ABI stay compatible, but inline function definitions may change — see below.
- a bump in **`z`** is a bug fix or a purely additive extension: upgrading never breaks a consumer.

@augmentcode augmentcode Bot Aug 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README.md:175: The new wording “a bump in z … upgrading never breaks a consumer” seems stronger than “bug fix”, since bugfixes can change observable behavior (and might break consumers depending on prior behavior). Would it help to clarify whether “never breaks” is meant strictly in the API/ABI sense (for a single-version rebuild) rather than behavioral compatibility?

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Because the library is header-only, **use a single libfn version per binary**. Mixing versions in one program is an ODR violation — and that includes two `z` releases of the *same* `y` line, whose inline definitions may differ even though the ABI matches.

Expand All @@ -182,6 +183,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for the development environment, building

* Gašper Ažman, for providing the inspiration in ["(Fun)ctional C++ and the M-word"][gasper-functional-presentation]
* Bartosz Milewski, for taking the time to explain [parametrised and graded monads][parametrised-and-graded-monads] and [effect systems][effect-systems]
* [Mykola Golubyev][mykola-golubyev], for implementing fixes in [znai][znai] needed by this project
* [Ripple][ripple], for allowing the main author the time to work on this library

## License
Expand All @@ -199,3 +201,5 @@ Distributed under the ISC License; see [LICENSE.md](LICENSE.md) for the terms.
[parametrised-and-graded-monads]: https://arxiv.org/pdf/2001.10274.pdf
[effect-systems]: https://www.doc.ic.ac.uk/~dorchard/publ/haskell14-effects.pdf
[ripple]: https://ripple.com/
[mykola-golubyev]: https://github.com/MykolaGolubyev
[znai]: https://github.com/testingisdocumenting/znai
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.9
0.1.0-rc1
1 change: 1 addition & 0 deletions examples/readme/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class Rational {
-> fn::expected<Rational, fn::copack_for<DivByZero, Overflow>>
{
if (d == 0) return fn::unexpected{fn::copack{DivByZero{}}};
// Note, std::gcd precondition is that `|n|` and `|d|` must both be representable.
if (n == std::numeric_limits<long long>::min() || d == std::numeric_limits<long long>::min())
return fn::unexpected{fn::copack{Overflow{}}};

Expand Down
6 changes: 3 additions & 3 deletions include/libfn_version.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
#define INCLUDE_LIBFN_VERSION

// Mode-less version for pfn, which never uses C++26 features.
#define LIBFN_VERSION_BASE v0_0_9
#define LIBFN_VERSION_BASE v0_1_rc1

#ifdef LIBFN_CXX26
#define LIBFN_VERSION v0_0_9_cxx26
#define LIBFN_VERSION v0_1_rc1_cxx26
#else
#define LIBFN_VERSION v0_0_9
#define LIBFN_VERSION v0_1_rc1
#endif

#endif // INCLUDE_LIBFN_VERSION
2 changes: 1 addition & 1 deletion ports/libfn/vcpkg.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "libfn",
"version-semver": "0.0.9",
"version-semver": "0.1.0-rc1",
"description": "Functional programming in C++",
"homepage": "https://github.com/libfn/functional",
"license": "ISC",
Expand Down