Skip to content

chore: release fp-library v0.14.0#22

Merged
nothingnesses merged 112 commits intomainfrom
release/fp-library-v0.14.0
Mar 29, 2026
Merged

chore: release fp-library v0.14.0#22
nothingnesses merged 112 commits intomainfrom
release/fp-library-v0.14.0

Conversation

@nothingnesses
Copy link
Copy Markdown
Owner

Summary

Release fp-library v0.14.0 with breaking changes and significant new functionality.

Highlights

  • New type classes: Extend, Comonad (Extend + Extract), MonadPlus (Monad + Alternative), Extract (renamed from Evaluable)
  • Step replaced by core::ops::ControlFlow with swapped-parameter HKT brands
  • Free monad refactored to CatList-paired representation (eliminates Bind-on-Pure nesting, uniformly O(1) bind)
  • MonadRec combinators: forever, while_some, until_some, repeat_m, while_m, until_m
  • Extend for collections: VecBrand and CatListBrand implement suffix-based Extend
  • Infrastructure: treefmt, git-hooks (pre-commit formatting, pre-push clippy/doc), TOML formatting via tombi, justfile as single source of truth

Breaking changes

  • Evaluable renamed to Extract (method evaluate -> extract)
  • Step enum removed (use core::ops::ControlFlow)
  • StepBrand/StepLoopAppliedBrand/StepDoneAppliedBrand renamed to ControlFlowBrand/ControlFlowBreakAppliedBrand/ControlFlowContinueAppliedBrand
  • TrySendThunkBrand removed
  • Extend trait requires A: Clone
  • Serde support for step/control-flow values removed

See fp-library/CHANGELOG.md for the full list.

Test plan

  • CI passes (fmt, clippy, doc, tests across feature combinations, benchmarks, cargo-deny)
  • Doc examples compile and demonstrate correct behavior
  • Property tests verify laws for all new type class implementations

- Add `WithIndex` base trait to the Indexed type classes listing
- Add `pipe` to the helper functions listing
- Remove spurious `<I>` type parameters from `ParFunctorWithIndex`
  and `ParFoldableWithIndex` in the parallel trait tables
Distinguish the standard free monad from the freer monad encoding,
correct attribution for the coroutine paper (Kawahara & Kameyama),
clarify that bluefin uses capability passing rather than ReaderT IO,
note that Effect (TypeScript) uses a fiber-based runtime not CPS,
and add missing entries (fused-effects, ZIO) to the implementations
table.
- Fix incorrect Send claim about Trampoline in README and lib.rs
- Fix "run" -> "evaluate" and misleading print comment in trampoline.rs
- Fix "to evaluable" -> "to evaluate" typo in evaluable.rs
- Document From conversion costs involving clone
- Add "Why there is no generic fix" section to deferrable.rs
- Expand Evaluable trait doc with semantic contract
- Create pub(crate) utils module with panic_payload_to_string helper
- Refactor TryThunk::catch_unwind and TryLazy::catch_unwind to use
  the shared helper instead of duplicated inline logic
- Add TryTrampoline::catch_unwind for consistency with TryThunk and
  TryLazy, using the same helper
- QW-6: Deprecate TryThunk::pure in favor of ok
- SC-3: Add inherent lift2 and then methods to TryThunk
- MP-5: Add From<TryTrampoline> for TryThunk (without unnecessary Send)
- MP-3: Add memoize() to TryThunk returning RcTryLazy
- MP-6: Add memoize_arc() to TryThunk (evaluates eagerly since inner
  closure is not Send)
- MP-1: Remove unnecessary Send bound from From<Trampoline> for Thunk
- MP-2: Remove unnecessary Send bounds from From<TryTrampoline> for
  RcTryLazy
- QW-5: Add Foldable impl for LazyBrand<ArcLazyConfig>
- SC-4: Add map and map_err methods to RcTryLazy and ArcTryLazy
- MP-3: Add memoize() to TryTrampoline
- MP-6: Add memoize_arc() to Thunk, Trampoline, and TryTrampoline
  (evaluates eagerly since inner closures are not Send)
- Add QuickCheck property tests for TryThunk (functor/monad laws,
  error short-circuit)
- Add QuickCheck property tests for TryTrampoline (functor/monad laws,
  error short-circuit)
- Add memoization and deferrable transparency tests for TryLazy
- Add panic poisoning tests for RcLazy and RcTryLazy
- Add ArcTryLazy thread safety test
- Add ThunkBrand tail_rec_m stack safety test at depth 1M
- Add TryTrampoline stack safety tests (tail_rec_m and bind chain)
Add #[inline] to trivial wrapper methods on Thunk, Lazy (both Rc and
Arc variants), and TryThunk. Fix unnecessary clone in Lazy::ref_map
for both RcLazy and ArcLazy by moving self directly into the closure.
Extend #[inline] coverage to TryThunk, TryTrampoline, and TryLazy
wrapper methods. Add a Stack Safety section to Thunk struct docs
explaining bind chain limitations and tail_rec_m/Trampoline alternatives.
Correct misleading claim that Thunk/Trampoline "re-run every time you
call .evaluate()"; evaluate() takes self by value so each instance runs
exactly once. Add comparison table rows for TryThunk, TryTrampoline,
and TryLazy in both README.md and lib.rs.
Add catch_unwind for ArcTryLazy mirroring the RcTryLazy version. Fill
test coverage for TryThunk lift2/then/memoize/memoize_arc and TryLazy
map/map_err for both Rc and Arc variants. Add generalized
catch_unwind_with accepting a custom panic payload converter on all
Try* types.
Generalize catch_unwind on TryTrampoline, RcTryLazy, and ArcTryLazy
to accept a custom Box<dyn Any + Send> -> E handler via
catch_unwind_with. Existing catch_unwind methods now delegate using
panic_payload_to_string.
Cover Thunk (map/bind chains), Trampoline (bind chains, tail_rec_m,
vs iterative), Lazy (first-access, cached-access, ref_map chains for
both Rc and Arc), and Free (left/right-associated binds, evaluate).
Add trybuild tests verifying that Thunk is !Send, Trampoline requires
'static, ArcLazy requires Send closures, and RcLazy is !Send.
Add resume() for step-by-step Free monad inspection, returning
Result<A, F<Free<F, A>>>. Add fold_free() for interpreting Free into
an arbitrary monad via NaturalTransformation. Add Map variant to
FreeInner for efficient map chains without type-erasure roundtrip.
Update Trampoline::map to use the new Map variant.
Delegate to lift2 for stack-safe combination of two Trampoline values
using Semigroup::append. Add Trampoline::empty() wrapping Monoid::empty
in Trampoline::pure.
Add associated type PointerBrand to LazyConfig trait, mapping
RcLazyConfig to RcBrand and ArcLazyConfig to ArcBrand. Enables
generic code to constrain pointer capabilities from a LazyConfig.
Add SendRefFunctor trait mirroring RefFunctor with Send bound on the
mapping function, following the Deferrable/SendDeferrable pattern.
Implement for LazyBrand<ArcLazyConfig> to give ArcLazy trait-level
ref_map with thread-safe functions.
Remove unused ArcLazyConfig/RcLazyConfig imports from try_thunk,
remove duplicate #[inline] attrs and simplify redundant closures in
try_lazy, add #[document_parameters] to try_lazy impl blocks, fix
NaturalTransformation doc attributes in free.
Add missing trait implementations, types, documentation, and tests
across the lazy evaluation hierarchy based on a comprehensive audit
of all 12 source files.

New type:
- SendThunk<'a, A>: thread-safe deferred computation (Send + 'a)

Trait implementations:
- RefFunctor, SendRefFunctor, Foldable, Semigroup, Monoid for TryLazy
- Semigroup, Monoid, bimap for TryTrampoline
- FunctorWithIndex, FoldableWithIndex for Thunk
- Eq, Ord for Lazy
- Deferrable for ArcLazy and ArcTryLazy

Structural changes:
- Split LazyConfig into LazyConfig + TryLazyConfig
- Make SendDeferrable extend Deferrable as supertrait
- Add brand type aliases (RcLazyBrand, ArcLazyBrand, etc.)
- Add From conversions for ArcLazy/ArcTryLazy from Thunk/Trampoline

Documentation:
- Add stack safety warnings to fold_free, TryThunk, Thunk::tail_rec_m
- Add panic docs to TryLazy::evaluate, rc_lazy_fix, arc_lazy_fix
- Fix stale references (Runnable, Free::roll, eval test names)
- Add "why no Functor" explanation to Lazy
- Add algebraic properties, Traversable limitation notes to TryThunk
- Add transparency law and examples to SendDeferrable
- Add when-to-use guidance to all Try* types
- Create docs/lazy-evaluation.md as user-facing reference
- Update docs/architecture.md to link to new reference

Tests:
- HKT-level trait tests for Thunk (Foldable, lift2, apply, Evaluable)
- Memoize/memoize_arc caching tests for Thunk and TryThunk
- QuickCheck bifunctor, error-channel monad, Semigroup/Monoid laws
- Thread safety test for TryThunk::memoize_arc
- Trampoline stack safety stress tests (200k iterations)

Project configuration:
- Fix direnv usage: use direnv allow, never suppress errors
- Make test output caching mandatory in CLAUDE.md
TrySendThunk<'a, A, E> wraps SendThunk<'a, Result<A, E>>, providing
the Send counterpart to TryThunk with error-aware combinators.

Inherent methods: new, ok, err, defer, pure, bind, map, map_err,
bimap, catch, evaluate, lift2, then, memoize_arc, catch_unwind,
catch_unwind_with.

Trait impls: Deferrable, SendDeferrable, Semigroup, Monoid, Debug,
From<TryThunk>, From<Result>, From<SendThunk>.

Includes 36 unit tests covering all combinators, thread safety,
memoize_arc with multiple threads, and From conversions.
ThunkBrand already has Extract, so it automatically becomes a Comonad.
Extend wraps the function application in a new deferred computation.
Includes property tests for comonad laws.
Each unique set of test arguments now gets its own independent cache
keyed by md5 hash. Running just test -p fp-library foo no longer
corrupts the full-suite cache. Remove the test-subset recipe since
just test <args> handles both full and subset runs with caching.
Add ref_map as inherent methods on both TryLazy variants, mirroring
the existing pattern on RcLazy/ArcLazy. Update RefFunctor and
SendRefFunctor trait impls to delegate to the inherent methods.
Document monad laws, error short-circuiting behavior, and limitations
('static constraint, no HKT brand, !Send, not memoized). Model after
TryThunk and Trampoline documentation.
Split the single impl block into three sections documenting the actual
minimal trait requirements of each method group: construction (needs
only 'static), functor operations (needs Functor), and evaluation
(needs Extract + Functor). Struct bounds remain Extract + Functor
because the Drop impl requires Extract for stack-safe Suspend cleanup.
Factor the shared iterative logic from evaluate and resume into a
to_view method that returns FreeStep::Done or FreeStep::Suspended.
Rewrite evaluate and resume as thin wrappers over to_view.

Add substitute_free which interprets Free<F, A> into Free<G, A> using
a natural transformation F ~> Free<G>, without requiring MonadRec on
the target (unlike fold_free).
Expose Free::resume on Trampoline and TryTrampoline as inherent methods,
enabling step-by-step introspection of deferred computations without
full evaluation.
Replace the recursive hoist_free implementation with one that uses
lift_f and bind to defer each Suspend layer's transformation into the
CatList. The actual work happens in evaluate's iterative loop, making
the call stack O(1) regardless of Suspend depth. Add a 100k-depth
stress test.
Remove the test-force recipe to prevent agents from bypassing the test
cache. Update CLAUDE.md to instruct agents to check cached output before
re-running tests. Fix stale cargo fmt reference.
…tors

Remove the test_forever_vec_empty test which hung indefinitely (forever
on Vec is genuinely non-terminating, not empty). Replace the doc example
with OptionBrand which short-circuits via None. Fix while_some,
until_some, while_m, and until_m doctests to use Cell for interior
mutability instead of mutable captured variables (Fn closures cannot
mutate captures).
Replace strong Rc/Arc clones captured in rc_lazy_fix and arc_lazy_fix
closures with Rc::downgrade/Arc::downgrade. The closure captures a Weak
reference; during evaluation, upgrade() succeeds because the outer
reference is still alive. If dropped without evaluation, no cycle exists
and memory is reclaimed. Remove memory leak caveats from documentation.
Add rc_try_lazy_fix and arc_try_lazy_fix for self-referential fallible
lazy values, analogous to rc_lazy_fix/arc_lazy_fix. Use weak references
from the start to prevent memory leaks on drop-without-evaluation.
Include tests for basic functionality, memoization, and thread safety.
Add evaluate_owned(&self) -> A where A: Clone to RcLazy, ArcLazy,
RcTryLazy, and ArcTryLazy. Eliminates the common .evaluate().clone()
pattern.
Both types use an iterative loop that calls the step function by shared
reference, so Clone is not needed. This reduces friction for callers
with non-Clone closures.
Add QuickCheck tests verifying SendDeferrable transparency and nesting
laws for SendThunk, ArcLazy, TrySendThunk, and ArcTryLazy. Add functor
identity/composition and monad identity/associativity law tests for
SendThunk's inherent map and bind methods.
- Add MonadRec equivalence law and nondeterministic termination caveat
  to trait documentation.
- Remove stale Step reference from lib.rs crate docs.
- Move prop_extract_map test from extract.rs to thunk.rs (it tests a
  Comonad law, not an Extract law).
- Add duplicate as a default trait method on Extend alongside the
  existing free function.
Add comments explaining why Extract cannot be relaxed on Free (Drop
requires matching struct bounds), why substitute_free is stack-safe
(bind defers recursion to CatList), and why LazyBrand cannot implement
Extend (requires Functor, which requires owned A).
Update README.md, lib.rs, lazy-evaluation.md, and benchmarking.md:
- Replace Evaluable references with Extract.
- Add Extend, Comonad, LazyConfig, TryLazyConfig, FreeStep to listings.
- Remove stale Step references, add missing SendThunk/TrySendThunk.
- Fix ResultWithErrBrand to ResultErrAppliedBrand in benchmarking.md.
- Add missing benchmark docs for Pair, Lazy Evaluation, CatList.
- Update benchmark commands to use just recipes.
- Correct SendThunk/TrySendThunk stack safety in lazy-evaluation.md.
- Alphabetize brands.rs entries.
- Ensure README.md and lib.rs feature lists are consistent.
Integrate treefmt-nix and git-hooks.nix into the Nix flake to provide
unified formatting and pre-commit/pre-push checks:

- treefmt orchestrates rustfmt, nixfmt, prettier, and tombi in one pass
- Pre-commit hook runs treefmt (formatting check)
- Pre-push hook runs clippy and cargo doc (warnings as errors)
- just fmt now calls treefmt instead of cargo fmt alone
- just clippy and just doc now treat warnings as errors
- CI fmt job uses treefmt (checks all file types, not just Rust)
- Upgrade actions/checkout from v4 to v6 in CI and release workflows
- Add .git-blame-ignore-revs for ignoring formatting commits
- Remove plans/lazy/ (implementation complete)
- Regenerate UI test .stderr files for shifted line numbers
- Apply treefmt to all existing files (markdown, TOML, YAML, Nix)
Add suffix-based Extend implementations for Vec and CatList, matching
PureScript's Array instance. extend(f, collection) applies f to each
suffix of the collection. Add Clone bound to Extend trait (required for
collection types to create suffix copies). Update existing Identity and
Thunk impls to match. Add property tests for associativity law.
Add reciprocal cross-references: Semimonad mentions Extend as its dual,
Monad mentions Comonad as its dual. Reword Deferrable/Extract from
"dual" to "inverse" (they operate at different abstraction levels, not
a categorical duality) and clarify that Deferrable is value-level while
Extract is brand-level.
Add MonadPlus as a blanket impl marker trait combining Monad and
Alternative. Documents the distributivity law (bind distributes over
alt) and left-zero law (bind on empty produces empty). VecBrand and
CatListBrand satisfy both laws; OptionBrand satisfies left-zero only
(its alt picks the first Some, breaking distributivity). Add QuickCheck
property tests for both laws.
Move extend_flipped, compose_co_kleisli, and compose_co_kleisli_flipped
from free-function-only to default trait methods (like duplicate). Free
functions now delegate to the trait methods. This allows implementors to
override them for efficiency. Enable document_module validation on
Extend and add doc examples to all trait methods and free functions. Fix
RUSTDOCFLAGS ordering in justfile and disambiguate extend doc links.
Left-zero (bind(empty(), f) == empty()) follows from Monad + Alternative
and was removed as a separate law in PureScript when MonadZero was
deprecated. Keep it documented as a "this also holds" note rather than
a required law.
Clippy, doc, bench, and deny CI jobs now use just recipes via
nix develop, ensuring CI uses the same flags as local development
(e.g., -D warnings on clippy, unicode check on doc). Test job
remains direct cargo for feature matrix support.
Remove hardcoded --all-features from just test so it accepts arbitrary
feature flags via args. just verify now passes --all-features
explicitly. CI test matrix now delegates to just test with per-matrix
feature flags, making the justfile the single source of truth for all
jobs.
Check IN_NIX_SHELL to determine if the Nix environment is already
active (set by nix develop and direnv). When set, skip the direnv
prefix since the toolchain is already available. Fixes CI failures
where direnv is not installed in the GitHub Actions runner.
@nothingnesses nothingnesses force-pushed the release/fp-library-v0.14.0 branch from 4609329 to 14bdc8b Compare March 29, 2026 21:58
@nothingnesses nothingnesses merged commit ca5ceac into main Mar 29, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant