Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 12 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ composition and types) layered over `pfn` (polyfills of C++23/26 vocabulary type
## Collaboration

- Pushing back **and** asking questions is welcome — a challenged design beats a silently implemented flawed one.
- Both user and yourself are fallible — take review feedback seriously, encourage using other LLMs for help and review.
- Test every design change against TYPE_ALGEBRA.md (see Docs and Code): it must be expressible there without contradicting the algebra's foundations — additions are welcome, contradictions are to be challenged.
- Always consider whether change or addition might cause the user consuming this library to write unsafe code unwittingly, and challenge such changes.

## CI

Expand All @@ -17,7 +20,7 @@ composition and types) layered over `pfn` (polyfills of C++23/26 vocabulary type

## Commits & GitHub text

- Trailer `Assisted-by: Claude:<your live model id>` (Linux-kernel convention), e.g. `claude-opus-4-8`. This replaces the harness's trailer boilerplate entirely — no `Co-Authored-By:`, no `Claude-Session:` URL. Same rule for GitHub issues, PRs and comments: `Assisted-by:` is welcome; no other footers or attribution boilerplate.
- Trailer `Assisted-by: <agent name>:<your live model id>` ([Linux-kernel convention](https://docs.kernel.org/process/coding-assistants.html)), e.g. `Claude:claude-opus-4-8`. This replaces the harness's trailer boilerplate entirely — no `Co-Authored-By:`, no `Claude-Session:` URL. Same rule for GitHub issues, PRs and comments: `Assisted-by:` is welcome; no other footers or attribution boilerplate.
- Offer commits; never commit without the user's confirmation — which may be relayed to a commit subagent by the parent that received it. Terse messages: imperative topic; body only when the change needs a *why* (the routing rule in Code names that case).
- If a pre-commit hook rewrites staged files, the commit aborts — re-stage the same files and retry once (CONTRIBUTING `## Pre-commit`).
- A feature or fix commit should include a test for the behaviour it changes; exceptions are allowed. The PR must contain such a test somewhere unless the behaviour is inherently untestable (for example, because of language or compiler limitations); explain the omission.
Expand All @@ -39,6 +42,7 @@ composition and types) layered over `pfn` (polyfills of C++23/26 vocabulary type
- Don't create `.md`/summary/planning files unless asked (memory files are exempt — see Memory).
- A new file's copyright year = the year it enters the codebase (the current year; if unsure, infer from the latest commit).
- In `include/` headers, anchor the standard library as `::std::`, never bare `std::` — a user's `fn::std` would otherwise win lookup inside namespace `fn`. Not needed in tests.
- Every major change or addition must be documented in TYPE_ALGEBRA.md (see Docs). Major changes are those that impact the design of code consuming this library.

## Layering

Expand Down Expand Up @@ -78,6 +82,12 @@ composition and types) layered over `pfn` (polyfills of C++23/26 vocabulary type

- Map: README.md = user-facing overview (purpose, usage, project shape, support surface; no agent directives, no internal mechanics; CI surfaced as evidence only, never mechanics); CONTRIBUTING.md = contributor facts (coding + tests standards, build environment, workflows, all CI details, mechanics of every aspect; no agent directives, no library usage); TYPE_ALGEBRA.md = the design document — the library's type algebra worked from first principles; CHANGELOG.md = design history (dated entries, newest first); docs/ = API reference (Doxygen + znai → Pages; also usage); CLAUDE.md = agent practice + guardrails pointing into the above.
- Fenced C++ examples in README.md and TYPE_ALGEBRA.md are generated from sources in `examples/` by `scripts/sync_md_examples.py` (pre-commit keeps them in sync) — edit the example source, never the fence; prose edits stay outside fences.
- When updating TYPE_ALGEBRA.md, follow the guidelines in the `document-guidelines` HTML comment at the bottom of the document.
- Living documents (README, CONTRIBUTING, docs/) are timeless present tense — no "now", "no longer", "previously". A change that obsoletes documented design gets a dated CHANGELOG.md entry in the same change, saying what it obsoleted and why.
- Recency-bias and wordiness defence (all human-readable text — docs, code comments, and the like; CHANGELOG.md and commit messages are exempt — change-perspective is their correct form): before keeping new text, test — (1) day-one: would this sentence exist had the feature or fix always been here? (2) effort: is detail sized by reader need, or by how hard the work was? — a hard-won bugfix earns no extra words; its history lives in `git log`/`git blame`/CHANGELOG.md; (3) placement: is it where a newcomer looks, or where your recent work pulls it? (4) economy: could fewer words say it as well? After editing, delegate a whole-file top-to-bottom reread to a subagent briefed as a first-time reader, blind to what changed — never review only your diff. Triage its findings: fix what your edit touches, surface the rest rather than rewriting unasked.
- Recency-bias and wordiness defence — applies to all prose (docs, code comments, and the like); CHANGELOG.md and commit messages are exempt, change-perspective being their correct form. Before keeping new text, apply all four tests:
- *Day-one*: would this sentence exist had the feature or fix always been here?
- *Effort*: is detail sized by reader need, or by how hard the work was? A hard-won bugfix earns no extra words; its history lives in `git log`/`git blame`/CHANGELOG.md.
- *Placement*: is it where a newcomer looks, or where your recent work pulls it?
- *Economy*: could fewer words say it as well?
- After editing prose, delegate a whole-file top-to-bottom reread to a subagent briefed as a first-time reader, blind to what changed — never review only your diff. Triage its findings: fix what your edit touches, surface the rest rather than rewriting unasked.
- On memory or practice changes, check the root `.md` files for drift from reality and **offer** fixes (CLAUDE.md = practice, README/CONTRIBUTING = facts).
90 changes: 88 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,102 @@ The headers rely on C++20 only, in every default-mode build. The C++23 validatio

## Building locally

Both `fn` (`include/fn`) and `pfn` (`include/pfn`) target C++20. The unit tests and examples build in C++20 by default. If you have a recent enough compiler, use the CMake option `VALIDATE_CXX23=ON` to additionally build them in C++23 (requires `LIBFN_TESTS=ON`). This also enables `tests/pfn/expected_validation.cpp` and `tests/pfn/optional_validation.cpp`, which run the `pfn` test suites against the standard library's own `std::expected` and `std::optional` — validating the polyfills, and the tests themselves, against the real thing.
Both `fn` (`include/fn`) and `pfn` (`include/pfn`) target C++20. By default, with no options configured, the build compiles the library, the unit tests, and the examples in C++20:

The C++26 mode is separate from the standard level: defining `LIBFN_CXX26` switches the library's internal type ordering to `std::type_order` (see the feature-reliance note above) and moves `fn` into its own ABI namespace, since the two orderings may disagree on `copack`/`choice` alternative order and the layouts must never link as one. The CMake option `LIBFN_CXX26=ON` enables the mode for this build tree, and `VALIDATE_CXX26=ON` (requires `LIBFN_TESTS=ON` and `LIBFN_CXX26=ON`) additionally builds the tests and examples in C++26 with the mode enabled.
```bash
mkdir .build && cd .build
Comment thread
Bronek marked this conversation as resolved.
cmake ..
cmake --build .
ctest --output-on-failure
```

The sections below describe other build configurations. They vary that `cmake` line and assume the same build directory. `CMAKE_BUILD_TYPE` behaves as usual in `cmake`, so it is omitted from the examples unless an option constrains it.

For a quick check of a single example without the full CMake/Catch2 setup:

```bash
g++ -std=c++20 -Iinclude examples/polygon/main.cpp -o /tmp/polygon
```

### C++23 and C++26

The unit tests and examples build in C++20 by default. If your compiler is modern enough (GCC 15; Clang 21), use the CMake option `VALIDATE_CXX23=ON` to also build them in C++23 (requires `LIBFN_TESTS=ON`). This enables `tests/pfn/expected_validation.cpp` and `tests/pfn/optional_validation.cpp`. These run the `pfn` test suites against the standard library's `std::expected` and `std::optional` to validate that the polyfills (and the tests themselves) behave exactly like the standard library implementations.

```bash
cmake -DVALIDATE_CXX23=ON ..
```

Setting the CMake option `LIBFN_CXX26=ON` enables C++26 compilation mode, which switches the library's internal type ordering to use C++26's `std::type_order` (see the feature-reliance note above). Because this can change the layout of `copack` and `choice` alternatives, the library uses a distinct ABI namespace to prevent binary compatibility issues. `LIBFN_CXX26=ON` does not select a language version in the compilation options; it needs to be set separately, e.g. with `CMAKE_CXX_STANDARD`, `CXXFLAGS` or in a consumer project.

Setting `VALIDATE_CXX26=ON` (requires `LIBFN_TESTS=ON` and `LIBFN_CXX26=ON`) additionally builds the tests and examples in C++26 mode, selecting the C++26 language version. Since `std::type_order` is only implemented by GCC 16, this requires GCC 16. If GCC 16 is not your default compiler, select it via `CMAKE_CXX_COMPILER` and `CMAKE_C_COMPILER`, or with both `CC` and `CXX` environment variables:

```bash
cmake -DLIBFN_CXX26=ON -DVALIDATE_CXX26=ON ..
```

### Sanitizers

Sanitizers require `CMAKE_BUILD_TYPE=Debug`. They are enabled by default for a top-level build when the build type is `Debug` or left unset (including the default build steps above). To build `Debug` without sanitizers:

```bash
cmake -DLIBFN_SANITIZERS=OFF -DCMAKE_BUILD_TYPE=Debug ..
```

The availability of sanitizers varies by platform and compiler:

* **Linux (GCC & Clang):** Enables Address (ASan), Leak (LSan), and Undefined Behavior (UBSan) sanitizers.
* **macOS (Apple Clang):** Enables ASan and UBSan. LSan is not supported on macOS.
* **Unsupported configurations:** The option is rejected (causes a CMake error) on:
* MSVC
* macOS with Homebrew GCC (due to `libasan` library path issues)
* macOS with Homebrew Clang (due to library mismatches that hang the process)

### Coverage

Setting `LIBFN_COVERAGE=ON` adds a `coverage` target that writes a `coverage.xml` report based on previously run tests. This requires `LIBFN_TESTS=ON`, a top-level build, and GCC or Clang:

```bash
cmake -DLIBFN_COVERAGE=ON ..
cmake --build .
ctest -L 'tests_.*'
cmake --build . --target coverage
```

**Requirements:**

* **gcovr 8.4+:** The coverage build uses the `--merge-lines` option, which was added in `gcovr` 8.4. Since package managers often distribute older versions, using a Python virtual environment (`venv`) is recommended as the easiest way to install a newer version.
* **Coverage tool:** The coverage tool must match your compiler:
* GCC uses `gcov`.
* Clang uses `llvm-cov gcov`.
* Apple Clang uses `llvm-cov` resolved via `xcrun -f`.
These are detected and configured automatically.

### Documentation

Setting `LIBFN_DOCS=ON` adds an `export_docs` target that generates the API reference in the `docs/` folder of your build directory. This requires `LIBFN_TESTS=ON` and a top-level build:

```bash
cmake -DLIBFN_DOCS=ON ..
cmake --build . --target export_docs
```

**Requirements:**
The following tools must be installed and available in your `PATH`:

* **Doxygen** 1.12.0
* **znai** 1.91 (requires Java/OpenJDK 21 or newer)
* **Graphviz** (provides the `dot` tool used by Doxygen)

For a reference environment with these exact tool versions, see [`ci/docs/Dockerfile`](ci/docs/Dockerfile).

**Validation:**
The documentation build does more than just generate HTML. It also verifies that:

* Every documented entity is present on the generated site.
* The API signatures in the documentation exactly match the C++ headers.

The target will fail if the documentation is out-of-date or has broken references.

## Unit tests

Although we aim for 100% unit-test coverage, executing every line and branch tells only part of the story in this project. Many important guarantees are compile-time properties, such as overload resolution, conversions, constraints and `noexcept` specifications. Tests must therefore exercise an interface with combinations of dimensions, not merely execute every line.
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ The library features demonstrated by the code example above:

The example also demonstrates how well libfn works with general programming idioms. `make` is a *smart constructor* — the only way to build a `Rational` — enforcing the type's invariants and returning `expected`: callers never need to re-check what the type guarantees. Treating *callables as values* lets operations such as `and_then` accept `make` whole, carrying its overload set.

These properties also make libfn a natural fit for asynchronous composition, such as coroutines or senders/receivers. Operations and monadic types alike are plain values: `and_then(f)` is a *description* of a step, executed only when a monad is piped into it (an input to the sequence, or the result of the preceding operation). A framework can hold the steps of a computation and apply them as results arrive, with a strongly typed error channel and no hidden control flow — exactly what such programming models need.

Beyond the example: `choice` (a monad over `copack`); the same operations over `optional` as over `expected`; simultaneous disjunction (using `operator|` to fallback-combine monadic computations) and its `fn::disjoin` fold; `fn::conjoin` for simultaneous product folds; tuple protocol in `pack` (`get<I>(p)` or structured bindings); `pack` and `copack` are both structural types (a `constexpr` value which may be used as a template parameter); support for immovable values and callables; an extensible pipeline, where a verb defined outside the library pipes exactly like the built-in ones; and more — see [examples/](examples/) and the [API reference][docs].

None of this is ad hoc: [TYPE_ALGEBRA.md](TYPE_ALGEBRA.md) derives the entire design from first principles — the algebra of products and sums behind `pack` and `copack`, the logic of monadic composition, and the compiler-checked laws that the library obeys.

## How

The library comes as two parts in one repository:
Expand All @@ -146,13 +146,17 @@ This library requires a total ordering of types, which the standard provides fro

## Using the library

The library is header-only. The CMake package exports two targets:
The library is header-only. The CMake package exports `libfn::fn` and `libfn::pfn`:

```cmake
find_package(libfn CONFIG REQUIRED)
target_link_libraries(main PRIVATE libfn::fn) # or libfn::pfn for the polyfills alone
```

A third target, `libfn::fn_cxx26`, is the same headers entered with the [`LIBFN_CXX26` mode](#implementation-note) selected; it carries both the mode and its C++26 language requirement, so a target opts in with one link line. Link exactly one of `libfn::fn` or `libfn::fn_cxx26` per target. `pfn` is mode-independent and has no such variant.
Comment thread
Bronek marked this conversation as resolved.

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).

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).
Expand Down
Loading