Revision history for Monad
0.3.0 2026-10-05T10:39:44+01:00
- [BREAKING] Monad::Maybe and Monad::Either now follow the standard
functional semantics, so that what you know from Haskell's Maybe /
Either or Scala's Option / Either transfers directly. Four
behaviours changed; see the MIGRATION section in each module's Pod.
- [BREAKING] Monad::Maybe.map is a lawful functor map: a Some stays a
Some even when the mapped function returns an undefined value. It
used to collapse to None, which broke functor composition. Spell
the old behaviour `$m.bind({ Monad::Maybe.from-defined(f($_)) })`
or `$m.map(&f).filter(*.defined)`.
- [BREAKING] Monad::Maybe.unit is total — unit(Nil) is a Some. A pure
that inspects its argument breaks the monad left-identity law.
- [BREAKING] Monad::Maybe.unwrap dies on a None instead of returning
Nil (it is fromJust: partial and loud). Use get-or-else, fold, or
the raw .value accessor.
- [BREAKING] Monad::Either.unwrap has been removed: it returned
whichever side was present, so it silently handed callers an error
message where they expected a result. Use unwrap-right,
unwrap-left, get-or-else, fold, or .value.
- Added Monad::Maybe.from-defined — the old unit behaviour under an
honest name, and the right constructor for lookups and other APIs
that signal absence with an undefined value.
- Added total eliminators and combinators to Monad::Maybe:
get-or-else, fold(:none!, :some!), filter, to-either, and the class
methods sequence / traverse (both short-circuit on the first None).
- Added to Monad::Either: map-left, bimap, get-or-else,
fold(:left!, :right!), swap (type parameters swap with the sides),
to-maybe, and the class methods sequence / traverse (the first Left
short-circuits and is the result, unchanged).
- Monad::Maybe and Monad::Either now convert to each other without a
circular compile-time dependency, so to-either / to-maybe work
whichever module you loaded (or if you loaded only one).
- Fixed: constructing from an instance mixed the new marker role on
top of the old one, so a value built by calling a constructor on
the opposite variant answered True to both is-some and is-none (or
both is-left and is-right), and a chain of maps grew one mixin per
step. Every Maybe / Either now has exactly one type per
parameterisation, whichever way it was built.
- unwrap-left / unwrap-right failure messages now show the value that
was actually there; gist renders undefined payloads without a
warning.
- Fixed: Monad::Maybe.map and Monad::Either.map / map-left / bimap
rebuilt their result via the invocant's own parameterisation, so a
typed `Monad::Maybe[Persona].some($p).map(*.name)` died with a
type check failure instead of returning a Some(Str). Lawful fmap
has signature `f a -> f b`, so these now build an
Any-parameterized result instead — the same rule sequence /
traverse already followed. Type parameterisation is a
constructor-side constraint, not something transformations
preserve. swap is unaffected: it already reparameterizes correctly
by transposing [L, R] to [R, L].
- Monad::Maybe and Monad::Either Pod rewritten: every method
documented with an example, plus laws and a 0.2 -> 0.3 MIGRATION
section. New test files cover the monad and functor laws for both
types, every new combinator, and cross-module conversion under
every load order.
- Monad::Maybe and Monad::Either are now subtype-friendly: every
operation builds its result through two overridable hooks,
`maybe-type` and `either-type`, instead of hard-coding
`Monad::Maybe` / `Monad::Either`. A type that composes the role
and overrides the pair keeps its own type all the way along a
chain — map, bind, filter, map-left, bimap, swap, sequence,
traverse and the constructors — and cross-type conversions
(to-either on the Maybe subtype, to-maybe on the Either subtype)
land on the subtype's counterpart rather than on the base. The
defaults are exactly the old behaviour, so nothing changes for
code that does not override them.
- Monad::Maybe and Monad::Either now build each result type once per
process and reuse it, instead of re-deriving it on every call. The
type a combinator returns was parameterised afresh each time, and
a constructor mixed the Some / None (or Left / Right) marker onto
every value it built; both are now cached per type hook and type
parameter, behind a lock so that the work happens exactly once no
matter how many threads ask at once. Chains are roughly twice as
fast single-threaded and rather more than that under concurrency,
and no operation touches the meta-object protocol on its hot path
any more. The types handed out are identical to the ones built
before, so nothing observable changes.
- Fixed: the lazily-required sibling module behind to-either /
to-maybe was memoised with `//=`, and what it memoises is a type
object — which is undefined, so the memo never took and every
single conversion re-ran the require. Conversions were an order of
magnitude slower than they looked (a module-repository lookup per
call), and concurrent ones re-entered module loading from several
threads at once. The memo now tests for absence rather than
definedness and takes a lock around the require.
- Monad::List, Monad::Reader, Monad::State and Monad::Writer are
unchanged.
0.2.1 2026-04-29T23:52:52+01:00
- Bump Github actions to use node 24+
0.2.0 2026-04-12T16:41:12+01:00
- Parameterize Monad::Maybe[::T], Monad::Either[::L, ::R],
Monad::List[::T], Monad::Writer[::A, ::W]. Adds compile-time type
checking on the contained value; legacy unparameterized usage is
unchanged (all parameters default to Any).
- Implemented as parametric roles rather than classes — Raku doesn't
support parametric classes. `~~` and `isa-ok` against the bare
role name continue to work.
- Monad::Reader and Monad::State stay as plain classes; their
underlying `run` callable is type-erased (it's a closure), so type
parameters would only be documentation. Use plain Monad::State and
document your state/value types in comments.
- Add Monad::List to META6.json provides (was missing in 0.1.2).
- Comprehensive parametric-usage test suite (t/07_parametric.t).
0.1.2 2024-11-XX
- Initial release with Monad, Monad::Maybe, Monad::Either,
Monad::List, Monad::Reader, Monad::State, Monad::Writer.