karpal-arrow
A complete category and arrow hierarchy for Rust: Semigroupoid, Category, Arrow, ArrowChoice, ArrowApply, ArrowLoop, ArrowZero, ArrowPlus, and three concrete implementations.
What's inside
Trait hierarchy
HKT2
└→ Semigroupoid compose(f, g)
└→ Category id()
└→ Arrow arr(f), first, second, split, fanout
├→ ArrowChoice left, right, splat, fanin
├→ ArrowApply app (≅ Monad)
├→ ArrowLoop loop_arrow (D: Default)
└→ ArrowZero zero_arrow
└→ ArrowPlus plus(f, g)
Semigroupoid and Category
Composable morphisms with an identity:
use ;
let f: = Boxnew;
let g: = Boxnew;
// compose: apply g then f
let fg = compose;
assert_eq!; // (3 * 2) + 1
// identity
let id = ;
assert_eq!;
Arrow
Lift pure functions and operate on products:
use ;
// arr: lift a function into an arrow
let double = arr;
let show = arr;
let pipeline = compose;
assert_eq!;
// first: apply to the first component of a pair
let f = ;
assert_eq!;
// split (***): two arrows in parallel
let f = split;
assert_eq!;
// fanout (&&&): feed one input to two arrows
let bounds = fanout;
assert_eq!;
ArrowChoice
Route through sum types (Result<L, R>):
use ;
// left: route Ok through the arrow, pass Err through
let f = ;
assert_eq!;
assert_eq!;
// fanin (|||): merge two arrows, one for each branch
let handle: = fanin;
assert_eq!;
assert_eq!;
ArrowApply
Apply arrows from within a computation (equivalent in power to Monad):
use ;
let app = ;
let double: = Boxnew;
assert_eq!;
ArrowLoop
Fixed-point combinator with D: Default as the initial feedback seed:
use ;
let f = ;
assert_eq!; // 5 + 0 (i32::default())
Concrete implementations
| Type | Representation | Traits |
|---|---|---|
| FnA | Box<dyn Fn(A) -> B> |
Semigroupoid through ArrowLoop (all except Zero/Plus) |
| KleisliF<M> | Box<dyn Fn(A) -> M::Of<B>> |
Full hierarchy; ArrowZero/ArrowPlus when M: Plus |
| CokleisliF<W> | Box<dyn Fn(W::Of<A>) -> B> |
Semigroupoid + Category (via impl_cokleisli! macro) |
KleisliF — arrows for monadic effects
use ;
use OptionF;
type KOpt = ;
// Compose monadic functions with automatic short-circuiting
let safe_recip: =
Boxnew;
let double: = Boxnew;
let pipeline = compose;
assert_eq!; // 1/4 * 2
assert_eq!; // short-circuits
// ArrowPlus: try first, fall back to second
let primary: = Boxnew;
let fallback: = Boxnew;
let with_fallback = plus;
assert_eq!;
CokleisliF — arrows from comonadic contexts
use ;
use ;
type CoNev = ;
// Extract head (identity for NonEmptyVec comonad)
let id = ;
let nev = new;
assert_eq!;
// Compose context-aware functions
let sum: =
Boxnew;
let show: =
Boxnew;
let pipeline = compose;
// extend applies sum to each suffix, then show reads the head
CokleisliF requires the impl_cokleisli! macro because W::Of<A>: Clone
cannot be expressed generically with GATs. Pre-generated instances:
IdentityF, OptionF, NonEmptyVecF. Use impl_cokleisli_env! for EnvF<E>.
Design notes
-
Clone + 'staticbounds: All type parameters requireClone + 'static.'staticforBox<dyn Fn>representations;Clonefor KleisliF'sfirst(the passthrough value must be cloned inside the monadic bind closure). -
ArrowLoop and strict evaluation: Haskell's
looprelies on laziness to tie the knot. Rust is strict, soloop_arrowrequiresD: Defaultand uses single-pass evaluation withD::default()as the initial feedback seed. -
Operator naming: Haskell's symbolic operators are mapped to descriptive names:
***→split,&&&→fanout,+++→splat,|||→fanin.
Features
| Feature | Default | Description |
|---|---|---|
std |
yes | Implies alloc; enables FnA, KleisliF, CokleisliF |
alloc |
no | Same instances (for no_std with allocator) |
License
MIT OR Apache-2.0