From e3e5a92f75ddafc96cc737d9d0b2273977c24f6f Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Sun, 21 Dec 2025 17:08:41 -0500 Subject: [PATCH 1/4] Add some (mostly trivial) `Duoidal` instances Many are isomorphic to `Identity` (and `Identity` is itself a new instance), but there is also a `Const` instance that additionally serves as justification for the particular representation we use (two distinct newtypes). --- core/src/Control/Duoidal.hs | 404 ++++++++++++++++++++++++++++++++++-- 1 file changed, 386 insertions(+), 18 deletions(-) diff --git a/core/src/Control/Duoidal.hs b/core/src/Control/Duoidal.hs index fca3fdc..3fa5841 100644 --- a/core/src/Control/Duoidal.hs +++ b/core/src/Control/Duoidal.hs @@ -141,16 +141,24 @@ module Control.Duoidal -- * instance helpers normalPure, + sequentialAp, sequentialBind, sequentialLiftA2, sequentialPure, -- * duoids from commutative `Monad`s Commutative (Commutative, getCommutative), + commutativeAp, ) where -import "base" Control.Applicative (Alternative, Applicative, empty, (<|>)) +import "base" Control.Applicative + ( Alternative, + Applicative, + Const (Const), + empty, + (<|>), + ) import "base" Control.Applicative qualified as Base ( liftA2, liftA3, @@ -163,7 +171,8 @@ import "base" Control.Applicative qualified as Base import "base" Control.Category ((.)) import "base" Control.Monad (Monad) import "base" Control.Monad qualified as Base - ( forever, + ( ap, + forever, join, liftM2, return, @@ -182,18 +191,25 @@ import "base" Data.Bitraversable qualified as Base bitraverse, ) import "base" Data.Bool (Bool) +import "base" Data.Complex (Complex) import "base" Data.Either (Either (Left, Right), either) import "base" Data.Eq (Eq) import "base" Data.Foldable (Foldable) import "base" Data.Foldable qualified as Base (for_, traverse_) import "base" Data.Function (const, ($)) import "base" Data.Functor (Functor, fmap) +import "base" Data.Functor.Identity (Identity) import "base" Data.Kind (Constraint, Type) -import "base" Data.Monoid (Monoid, mempty) -import "base" Data.Ord (Ord) -import "base" Data.Semigroup (Semigroup, (<>)) +import "base" Data.Maybe (Maybe) +import "base" Data.Monoid (Dual, Monoid, Sum, mempty) +import "base" Data.Monoid qualified as Monoid +import "base" Data.Ord (Down, Ord) +import "base" Data.Proxy (Proxy) +import "base" Data.Semigroup (Max, Min, Semigroup, (<>)) +import "base" Data.Semigroup qualified as Semigroup import "base" Data.Traversable (Traversable) import "base" Data.Traversable qualified as Base (for, traverse) +import "base" Data.Tuple (Solo) import "base" GHC.TypeError (ErrorMessage (Text), TypeError) import "base" System.IO (IO) import "base" Text.Read (Read) @@ -467,6 +483,11 @@ sequentialLiftA2 :: Sequential f b -> Sequential f c sequentialLiftA2 f (Sequential a) = Sequential . Base.liftA2 f a . getSequential +{-# DEPRECATED sequentialLiftA2 "use ‘sequentialAp’ instead" #-} + +sequentialAp :: + (Monad f) => Sequential f (a -> b) -> Sequential f a -> Sequential f b +sequentialAp (Sequential f) = Sequential . Base.ap f . getSequential sequentialBind :: (Monad f) => Sequential f a -> (a -> Sequential f b) -> Sequential f b @@ -476,24 +497,328 @@ sequentialBind (Sequential a) f = Sequential $ a Base.>>= (getSequential . f) -- | Commutative `Monad`s form a duoid with themselves. -- --- __NB__: Don’t use this newtype on a non-commutative Monad. +-- You can use this with @DerivingVia@ to create instances for your own +-- commutative `Monad`s. +-- +-- For existing types, instances for types in base should be available here, +-- but those for other packages may not exist. For those that don’t, you can +-- +-- 1. wrap the type in `Commutative` when you need the instance, +-- 2. define orphan instances that look like the instances for `Commutative`, or +-- 3. use the provided operations (like `commutativeAp`) directly. +-- +-- Some examples of commutative monads: +-- - those isomorphic to `Identity` (many newtypes fall into this bucket) +-- - reader (@->@) +-- - `Maybe` +-- - `Proxy` +-- +-- You can also wrap types that already have `Duoidal` instances in +-- `Commutative` as well (as long as they have an unwrapped `Monad` instance). +-- If their existing `Duoidal` instance isn’t the commutative one, the +-- `Commutative` wrapper will give you the commutative one. I don’t know if +-- this one is correct, but if you have a commutative writer (say, @`Writer` +-- (`Set` `Char`)@), wrapping it in `Commutative` would give you a `Duoidal` +-- instance that behaves commutatively. However, @`Set` a@ should already have +-- a commutative `Duoid` instance, so I don’t think it actually buys you +-- anything. +-- +-- __NB__: Don’t use this newtype to turn a non-commutative `Monad` into a +-- duoid. type Commutative :: forall {k}. (k -> Type) -> k -> Type newtype Commutative f a = Commutative {getCommutative :: f a} deriving stock (Eq, Ord, Read, Show, Functor, Foldable, Traversable) -instance (Applicative f) => Applicative (Parallel (Commutative f)) where - pure = Parallel . Commutative . Base.pure - liftA2 f (Parallel (Commutative a)) (Parallel (Commutative b)) = - Parallel . Commutative $ Base.liftA2 f a b +instance (Monad f) => Applicative (Commutative f) where + pure = Commutative . Base.return + Commutative f <*> Commutative a = Commutative $ Base.ap f a -instance (Applicative f) => Applicative (Sequential (Commutative f)) where - pure = Sequential . Commutative . Base.pure - liftA2 f (Sequential (Commutative a)) (Sequential (Commutative b)) = - Sequential . Commutative $ Base.liftA2 f a b +instance (Monad f) => Monad (Commutative f) where + Commutative a >>= f = Commutative $ a Base.>>= (getCommutative . f) + +commutativeAp :: + (Monad f) => + Parallel f (a -> b) -> + Parallel f a -> + Parallel f b +commutativeAp (Parallel f) = Parallel . Base.ap f . getParallel + +instance (Monad f) => Applicative (Parallel (Commutative f)) where + pure = normalPure + (<*>) = commutativeAp + +instance (Monad f) => Applicative (Sequential (Commutative f)) where + pure = sequentialPure + (<*>) = sequentialAp instance (Monad f) => Monad (Sequential (Commutative f)) where - Sequential (Commutative a) >>= f = - Sequential . Commutative $ a Base.>>= (getCommutative . getSequential . f) + (>>=) = sequentialBind + +instance (Monad f) => Normal (Commutative f) + +-- `Complex` is a commutative duoidal functor + +instance Applicative (Parallel Complex) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Complex) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Complex) where + (>>=) = sequentialBind + +instance Normal Complex + +-- `Down` is a commutative duoidal functor + +instance Applicative (Parallel Down) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Down) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Down) where + (>>=) = sequentialBind + +instance Normal Down + +-- `Dual` is a commutative duoidal functor + +instance Applicative (Parallel Dual) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Dual) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Dual) where + (>>=) = sequentialBind + +instance Normal Dual + +-- `Monoid.First` is a commutative duoidal functor + +instance Applicative (Parallel Monoid.First) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Monoid.First) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Monoid.First) where + (>>=) = sequentialBind + +instance Normal Monoid.First + +-- `Semigroup.First` is a commutative duoidal functor + +instance Applicative (Parallel Semigroup.First) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Semigroup.First) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Semigroup.First) where + (>>=) = sequentialBind + +instance Normal Semigroup.First + +-- `Identity` is a commutative duoidal functor + +instance Applicative (Parallel Identity) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Identity) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Identity) where + (>>=) = sequentialBind + +instance Normal Identity + +-- `Monoid.Last` is a commutative duoidal functor + +instance Applicative (Parallel Monoid.Last) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Monoid.Last) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Monoid.Last) where + (>>=) = sequentialBind + +instance Normal Monoid.Last + +-- `Semigroup.Last` is a commutative duoidal functor + +instance Applicative (Parallel Semigroup.Last) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Semigroup.Last) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Semigroup.Last) where + (>>=) = sequentialBind + +instance Normal Semigroup.Last + +-- `Max` is a commutative duoidal functor + +instance Applicative (Parallel Max) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Max) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Max) where + (>>=) = sequentialBind + +instance Normal Max + +-- `Maybe` is a commutative duoidal functor + +instance Applicative (Parallel Maybe) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Maybe) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Maybe) where + (>>=) = sequentialBind + +instance Normal Maybe + +-- `Min` is a commutative duoidal functor + +instance Applicative (Parallel Min) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Min) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Min) where + (>>=) = sequentialBind + +instance Normal Min + +-- `Monoid.Product` is a commutative duoidal functor + +instance Applicative (Parallel Monoid.Product) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Monoid.Product) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Monoid.Product) where + (>>=) = sequentialBind + +instance Normal Monoid.Product + +-- `Proxy` is a commutative duoidal functor + +instance Applicative (Parallel Proxy) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Proxy) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Proxy) where + (>>=) = sequentialBind + +instance Normal Proxy + +-- `Solo` is a commutative duoidal functor + +instance Applicative (Parallel Solo) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Solo) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Solo) where + (>>=) = sequentialBind + +instance Normal Solo + +-- `Sum` is a commutative duoidal functor + +instance Applicative (Parallel Sum) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential Sum) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential Sum) where + (>>=) = sequentialBind + +instance Normal Sum + +-- reader is a commutative duoidal functor + +instance Applicative (Parallel ((->) r)) where + pure = normalPure + (<*>) = commutativeAp + +instance Applicative (Sequential ((->) r)) where + pure = sequentialPure + (<*>) = sequentialAp + +instance Monad (Sequential ((->) r)) where + (>>=) = sequentialBind + +instance Normal ((->) r) + +-- Const + +instance (Monoid a) => Applicative (Parallel (Const a)) where + pure = normalPure + liftA2 f (Parallel a) (Parallel b) = Parallel $ liftA2 f a b + +instance (Monoid a) => Applicative (Sequential (Const a)) where + pure = Sequential . pure + liftA2 f (Sequential a) = Sequential . Base.liftA2 f a . getSequential + +-- | The `Const` duoidal functor provides an illustration of why we need to have +-- both `Parallel` and `Sequential` newtypes – relying on the underlying +-- `Applicative` (and only having the `Sequential` newtype) would mean that +-- any duoidal structure would only have a `Monad` available under +-- `Sequential`, which would be a prettty serious impact. On the other hand, +-- relying on the underlying `Monad` (and only having the `Parallel` newtype) +-- is much more natural, but `Const`, for example, having a `Monad` instance +-- would make it basically useless, and the more interesting `Applicative` +-- instance would only be available under the `Parallel` netwype. The current +-- structure allows either the `Applicative` or `Monad` instance to be the one +-- exposed directly. +instance (Monoid a) => Monad (Sequential (Const a)) where + Sequential (Const a) >>= _ = Sequential $ Const a -- Either @@ -509,7 +834,7 @@ instance (Semigroup e) => Applicative (Parallel (Either e)) where instance (Semigroup e) => Applicative (Sequential (Either e)) where pure = sequentialPure - liftA2 = sequentialLiftA2 + (<*>) = sequentialAp instance (Semigroup e) => Monad (Sequential (Either e)) where (>>=) = sequentialBind @@ -539,7 +864,7 @@ instance Applicative (Parallel IO) where instance Applicative (Sequential IO) where pure = sequentialPure - liftA2 = sequentialLiftA2 + (<*>) = sequentialAp instance Monad (Sequential IO) where (>>=) = sequentialBind @@ -584,3 +909,46 @@ instance (Duoid a) => Monad (Sequential ((,) a)) where -- | A writer is a `Normal` `Duoidal` functor when the writee is a -- `Duoid.Normal` `Duoid`. instance (Duoid.Normal a) => Normal ((,) a) + +instance (Duoid a, Duoid b) => Applicative (Parallel ((,,) a b)) where + pure = Parallel . (pempty,pempty,) + liftA2 f (Parallel (a, b, x)) (Parallel (a', b', y)) = + Parallel (a |-| a', b |-| b', f x y) + +instance (Duoid a, Duoid b) => Applicative (Sequential ((,,) a b)) where + pure = Sequential . (sempty,sempty,) + liftA2 = Base.liftM2 + +instance (Duoid a, Duoid b) => Monad (Sequential ((,,) a b)) where + Sequential (u, v, a) >>= k = + case k a of Sequential (u', v', b) -> Sequential (u >-> u', v >-> v', b) + +-- | A writer is a `Normal` `Duoidal` functor when the writee is a +-- `Duoid.Normal` `Duoid`. +instance (Duoid.Normal a, Duoid.Normal b) => Normal ((,,) a b) + +instance + (Duoid a, Duoid b, Duoid c) => + Applicative (Parallel ((,,,) a b c)) + where + pure = Parallel . (pempty,pempty,pempty,) + liftA2 f (Parallel (a, b, c, x)) (Parallel (a', b', c', y)) = + Parallel (a |-| a', b |-| b', c |-| c', f x y) + +instance + (Duoid a, Duoid b, Duoid c) => + Applicative (Sequential ((,,,) a b c)) + where + pure = Sequential . (sempty,sempty,sempty,) + liftA2 = Base.liftM2 + +instance (Duoid a, Duoid b, Duoid c) => Monad (Sequential ((,,,) a b c)) where + Sequential (u, v, w, a) >>= k = + case k a of + Sequential (u', v', w', b) -> Sequential (u >-> u', v >-> v', w >-> w', b) + +-- | A writer is a `Normal` `Duoidal` functor when the writee is a +-- `Duoid.Normal` `Duoid`. +instance + (Duoid.Normal a, Duoid.Normal b, Duoid.Normal c) => + Normal ((,,,) a b c) From 6727ae97d84cf724f2148dba315b442fbcd2982c Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Sun, 21 Dec 2025 17:11:08 -0500 Subject: [PATCH 2/4] Improve docs for the non-core packages --- .cache/vale/Vocab/duoids/accept.txt | 1 + .cache/vale/config/vocabularies/duoids/accept.txt | 1 + .config/project/default.nix | 5 ++++- algebraic-graphs/README.md | 6 +++--- core/duoids.cabal | 3 ++- hedgehog/README.md | 4 +--- transformers/README.md | 6 +++--- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.cache/vale/Vocab/duoids/accept.txt b/.cache/vale/Vocab/duoids/accept.txt index 928bd6a..df3c51a 100644 --- a/.cache/vale/Vocab/duoids/accept.txt +++ b/.cache/vale/Vocab/duoids/accept.txt @@ -5,6 +5,7 @@ garnix [Nn]ix Pfeil ShellCheck +duoid API bugfix comonad diff --git a/.cache/vale/config/vocabularies/duoids/accept.txt b/.cache/vale/config/vocabularies/duoids/accept.txt index 928bd6a..df3c51a 100644 --- a/.cache/vale/config/vocabularies/duoids/accept.txt +++ b/.cache/vale/config/vocabularies/duoids/accept.txt @@ -5,6 +5,7 @@ garnix [Nn]ix Pfeil ShellCheck +duoid API bugfix comonad diff --git a/.config/project/default.nix b/.config/project/default.nix index df97990..e980d59 100644 --- a/.config/project/default.nix +++ b/.config/project/default.nix @@ -51,7 +51,10 @@ ## i686-linux again … programs.prettier.enable = lib.mkForce true; }; - vale.enable = true; + vale = { + enable = true; + vocab.${config.project.name}.accept = ["duoid"]; + }; }; ## CI diff --git a/algebraic-graphs/README.md b/algebraic-graphs/README.md index 7b05d13..6056eb8 100644 --- a/algebraic-graphs/README.md +++ b/algebraic-graphs/README.md @@ -3,12 +3,12 @@ [![Packaging status](https://repology.org/badge/tiny-repos/haskell:duoids.svg)](https://repology.org/project/haskell:duoids/versions) [![latest packaged versions](https://repology.org/badge/latest-versions/haskell:duoids.svg)](https://repology.org/project/haskell:duoids/versions) -Unifying parallel and sequential operations - -Duoids relate a pair of monoids, where one can be seen as “parallel” and the other “sequential”. +[Duoids](https://hackage.haskell.org/package/duoids) applied to the [algebraic-graphs](https://hackage.haskell.org/package/algebraic-graphs) package. ## usage +There are `Duoid` instances for the various graph representations in `algebraic-graphs`. There are also some implementations of `Duoid` methods to make it easy to provide `Duoid` instances for _other_ `Graph` instances. + ## versioning This project largely follows the [Haskell Package Versioning Policy](https://pvp.haskell.org/) (PVP), but is more strict in some ways. diff --git a/core/duoids.cabal b/core/duoids.cabal index 3ab7cc9..fb3636c 100644 --- a/core/duoids.cabal +++ b/core/duoids.cabal @@ -137,7 +137,8 @@ common defaults library import: defaults - hs-source-dirs: src + hs-source-dirs: + src exposed-modules: Control.Duoidal Control.Duoidal.Either diff --git a/hedgehog/README.md b/hedgehog/README.md index 7b05d13..f8d8814 100644 --- a/hedgehog/README.md +++ b/hedgehog/README.md @@ -3,9 +3,7 @@ [![Packaging status](https://repology.org/badge/tiny-repos/haskell:duoids.svg)](https://repology.org/project/haskell:duoids/versions) [![latest packaged versions](https://repology.org/badge/latest-versions/haskell:duoids.svg)](https://repology.org/project/haskell:duoids/versions) -Unifying parallel and sequential operations - -Duoids relate a pair of monoids, where one can be seen as “parallel” and the other “sequential”. +Tools to help test your [duoids](https://hackage.haskell.org/package/duoids) usage. ## usage diff --git a/transformers/README.md b/transformers/README.md index 7b05d13..bb114f1 100644 --- a/transformers/README.md +++ b/transformers/README.md @@ -3,12 +3,12 @@ [![Packaging status](https://repology.org/badge/tiny-repos/haskell:duoids.svg)](https://repology.org/project/haskell:duoids/versions) [![latest packaged versions](https://repology.org/badge/latest-versions/haskell:duoids.svg)](https://repology.org/project/haskell:duoids/versions) -Unifying parallel and sequential operations - -Duoids relate a pair of monoids, where one can be seen as “parallel” and the other “sequential”. +[Duoids](https://hackage.haskell.org/package/duoids) applied to the [transformers](https://hackage.haskell.org/package/transformers) package. ## usage +There is a `DuoidalTrans` class that works very much like `MonadTrans`. There are also `Duoidal` instances for various transformers. + ## versioning This project largely follows the [Haskell Package Versioning Policy](https://pvp.haskell.org/) (PVP), but is more strict in some ways. From b2844cbd2e5c02aa3b6c7dd54c20a03448199e47 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Sun, 21 Dec 2025 22:57:53 -0500 Subject: [PATCH 3/4] Refresh the hedgehog license report --- hedgehog/docs/license-report.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hedgehog/docs/license-report.md b/hedgehog/docs/license-report.md index c0a5c21..56fa8f9 100644 --- a/hedgehog/docs/license-report.md +++ b/hedgehog/docs/license-report.md @@ -17,7 +17,7 @@ Bold-faced **`package-name`**s denote standard libraries bundled with `ghc-9.10. | Name | Version | [SPDX](https://spdx.org/licenses/) License Id | Description | Depended upon by | | --- | --- | --- | --- | --- | -| `ansi-terminal` | [`1.1.3`](http://hackage.haskell.org/package/ansi-terminal-1.1.3) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ansi-terminal-1.1.3/src/LICENSE) | Simple ANSI terminal support | `concurrent-output`, `hedgehog` | +| `ansi-terminal` | [`1.1.4`](http://hackage.haskell.org/package/ansi-terminal-1.1.4) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ansi-terminal-1.1.4/src/LICENSE) | Simple ANSI terminal support | `concurrent-output`, `hedgehog` | | `ansi-terminal-types` | [`1.1.3`](http://hackage.haskell.org/package/ansi-terminal-types-1.1.3) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ansi-terminal-types-1.1.3/src/LICENSE) | Types and functions used to represent SGR aspects | `ansi-terminal` | | **`array`** | [`0.5.7.0`](http://hackage.haskell.org/package/array-0.5.7.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/array-0.5.7.0/src/LICENSE) | Mutable and immutable arrays | `binary`, `containers`, `deepseq`, `ghc`, `ghci`, `pretty-show`, `stm`, `text` | | `async` | [`2.2.5`](http://hackage.haskell.org/package/async-2.2.5) | [`BSD-3-Clause`](http://hackage.haskell.org/package/async-2.2.5/src/LICENSE) | Run IO operations asynchronously and wait for their results | `concurrent-output`, `hedgehog`, `lifted-async` | @@ -64,12 +64,12 @@ Bold-faced **`package-name`**s denote standard libraries bundled with `ghc-9.10. | **`semaphore-compat`** | [`1.0.0`](http://hackage.haskell.org/package/semaphore-compat-1.0.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/semaphore-compat-1.0.0) | Cross-platform abstraction for system semaphores | `ghc` | | `splitmix` | [`0.1.3.1`](http://hackage.haskell.org/package/splitmix-0.1.3.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/splitmix-0.1.3.1/src/LICENSE) | Fast Splittable PRNG | `random` | | **`stm`** | [`2.5.3.1`](http://hackage.haskell.org/package/stm-2.5.3.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/stm-2.5.3.1/src/LICENSE) | Software Transactional Memory | `async`, `concurrent-output`, `exceptions`, `ghc`, `hedgehog`, `monad-control`, `transformers-base` | -| `tagged` | [`0.8.9`](http://hackage.haskell.org/package/tagged-0.8.9) | [`BSD-3-Clause`](http://hackage.haskell.org/package/tagged-0.8.9/src/LICENSE) | Haskell 98 phantom types to avoid unsafely passing dummy arguments | `boring`, `distributive` | +| `tagged` | [`0.8.10`](http://hackage.haskell.org/package/tagged-0.8.10) | [`BSD-3-Clause`](http://hackage.haskell.org/package/tagged-0.8.10/src/LICENSE) | Haskell 98 phantom types to avoid unsafely passing dummy arguments | `boring`, `distributive` | | **`template-haskell`** | [`2.22.0.0`](http://hackage.haskell.org/package/template-haskell-2.22.0.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/template-haskell-2.22.0.0/src/LICENSE) | Support library for Template Haskell | `bytestring`, `containers`, `exceptions`, `filepath`, `ghc`, `ghci`, `hedgehog`, `os-string`, `primitive`, `tagged`, `text` | | `terminal-size` | [`0.3.4`](http://hackage.haskell.org/package/terminal-size-0.3.4) | [`BSD-3-Clause`](http://hackage.haskell.org/package/terminal-size-0.3.4/src/LICENSE) | Get terminal window height and width | `concurrent-output` | | **`text`** | [`2.1.1`](http://hackage.haskell.org/package/text-2.1.1) | [`BSD-2-Clause`](http://hackage.haskell.org/package/text-2.1.1/src/LICENSE) | An efficient packed Unicode text type. | `concurrent-output`, `hashable`, `hedgehog`, `pretty-show`, `wl-pprint-annotated` | | **`time`** | [`1.12.2`](http://hackage.haskell.org/package/time-1.12.2) | [`BSD-2-Clause`](http://hackage.haskell.org/package/time-1.12.2/src/LICENSE) | A time library | `directory`, `ghc`, `hedgehog`, `hpc`, `unix` | -| **`transformers`** | [`0.6.1.1`](http://hackage.haskell.org/package/transformers-0.6.1.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/transformers-0.6.1.1/src/LICENSE) | Concrete functor and monad transformers | `barbies`, `boring`, `concurrent-output`, `constraints`, `distributive`, `exceptions`, `ghc`, `ghci`, `hedgehog`, `mmorph`, `monad-control`, `mtl`, `primitive`, `random`, `resourcet`, `safe-exceptions`, `tagged`, `transformers-base`, `transformers-compat`, `unliftio-core` | +| **`transformers`** | [`0.6.1.1`](http://hackage.haskell.org/package/transformers-0.6.1.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/transformers-0.6.1.1/src/LICENSE) | Concrete functor and monad transformers | `barbies`, `boring`, `concurrent-output`, `constraints`, `distributive`, `exceptions`, `ghc`, `ghci`, `hedgehog`, `mmorph`, `monad-control`, `mtl`, `primitive`, `random`, `resourcet`, `safe-exceptions`, `transformers-base`, `transformers-compat`, `unliftio-core` | | `transformers-base` | [`0.4.6`](http://hackage.haskell.org/package/transformers-base-0.4.6) | [`BSD-3-Clause`](http://hackage.haskell.org/package/transformers-base-0.4.6/src/LICENSE) | Lift computations from the bottom of a transformer stack | `hedgehog`, `lifted-async`, `lifted-base`, `monad-control` | | `transformers-compat` | [`0.7.2`](http://hackage.haskell.org/package/transformers-compat-0.7.2) | [`BSD-3-Clause`](http://hackage.haskell.org/package/transformers-compat-0.7.2/src/LICENSE) | A small compatibility shim for the transformers library | `mmorph`, `monad-control`, `transformers-base` | | **`unix`** | [`2.8.5.1`](http://hackage.haskell.org/package/unix-2.8.5.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/unix-2.8.5.1/src/LICENSE) | POSIX functionality | `concurrent-output`, `directory`, `ghc`, `ghc-boot`, `ghci`, `process`, `semaphore-compat` | From e46fd4664e777b16bbc30646544d6cd64a0ab5a2 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Sun, 21 Dec 2025 22:58:49 -0500 Subject: [PATCH 4/4] Add a comparison to multi-except Very cursory, but at least noting that it exists. --- core/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/README.md b/core/README.md index d2d6cea..5b169dc 100644 --- a/core/README.md +++ b/core/README.md @@ -107,12 +107,16 @@ The `async` package has a `Concurrently` newtype over `IO` that’s the same as ### [either](https://hackage.haskell.org/package/either) -The `either` package has a `Validation` type that’s isomorphic to `Either` and has an applicative instance that’s the same as the `Parallel (Either e)` instance here. However, it doesn’t have anything like the `Duoidal` class, and so requires manual conversion back and forth between `Either` and `Validation`. +The `either` package has a `Validation` type that’s isomorphic to `Either` and has an `Applicative` instance that’s the same as the `Parallel (Either e)` instance here. However, it doesn’t have anything like the `Duoidal` class, and so requires manual conversion back and forth between `Either` and `Validation`. ### [Haskerwaul](https://github.com/sellout/haskerwaul#readme) A broader package that contains a more general (category polymorphic) implementation of duoids. It’s also much less pragmatic. +### [multi-except](https://hackage.haskell.org/package/multi-except) + +This seems like another approach to `Validation`, but I haven’t used it. + ### [United Monoids](https://github.com/snowleopard/united) Andrey Mokhov’s independently-discovered implementation. I think united monoids are normal duoids in **Set**. United Monoids also offers a `Semilattice` class and avoids newtypes by treating the parallel semigroup (`overlay` in United Monoids) as primary, with sequential (`connect`) as an additional operation. It also forces both identities to be the same.