packages feed

kleisli 0.0.2 → 0.0.3

raw patch · 4 files changed

+139/−6 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Data.Kleisli: mkContraKleisli' :: Profunctor p => p a b -> ContraKleisli' p b a
+ Data.Kleisli: mkKleisli' :: Profunctor p => p a b -> Kleisli' p a b
+ Data.Kleisli: mkProKleisli' :: Profunctor p => p a b -> ProKleisli' p a b

Files

README.md view
@@ -57,6 +57,105 @@ kleisli' :: Iso (Kleisli' p a b) ... (p a b) ... ``` +## Design: types as interfaces++This library follows an approach where the **data type is the API** and+**type-class instances are the interface**. There are almost no top-level+functions — users interact with these newtypes entirely through standard+algebraic vocabulary (`fmap`, `>>=`, `lmap`, `arr`, `contramap`, `divide`,+etc.) that they already know.++### How it works++A single representation (`p a (f b)`) is wrapped in three newtypes that differ+only in which type parameter is "outermost" — i.e. which parameter appears last+and therefore determines which type classes the newtype can inhabit. The+newtypes carry no runtime payload beyond what `p a (f b)` already is; they+exist solely to place the type in the right position for GHC's instance+resolution to fire.++The few top-level values that do exist cover the gaps that type classes cannot+express:++- **Isomorphisms** — there is no standard `IsomorphicTo` class that yields both+  directions, so `_Kleisli_ProKleisli` and friends are explicit `Iso` values.+- **Natural-transformation maps** (`hoistKleisli`, `pureKleisli`, etc.) —+  mapping over an inner functor layer is not captured by `Functor` (which+  operates on the outermost parameter), so a small family of combinators is+  provided.++Everything else is an instance.++### Benefits++- **Vocabulary reuse.** Users do not learn a new API. If you know `Profunctor`+  you already know how to use `ProKleisli`; if you know `Divisible` you already+  know how to use `ContraKleisli`. Every generic combinator, tutorial, and+  intuition that applies to those classes applies here without modification.++- **Composability for free.** Because the interface is expressed through+  standard classes, values of these types compose with any other code written+  against those same abstractions — lens combinators, servant routing,+  contravariant logging, arrow notation, selective functors, and so on — with+  no adapter or conversion layer.++- **Minimal surface area.** A small export list means fewer names to remember,+  fewer possible breakages across versions, and fewer degrees of freedom for+  API design mistakes. The type and its instances *are* the documentation.++- **Derivability.** `DerivingVia` lets GHC generate correct, law-abiding+  instances mechanically from an existing carrier (`Star`, `ReaderT`,+  `Arrow.Kleisli`, `Op`). The module author writes each instance at most once;+  the compiler writes the rest.++### Trade-offs++- **Discoverability.** The real functionality lives in instance heads, not in a+  list of named functions. Users must know (or look up) which class provides+  the operation they want. Haddock instance lists help, but they are less+  immediately scannable than a flat function list.++- **Type errors.** When an instance is missing or a constraint doesn't match,+  GHC's error messages refer to the class and the full newtype-wrapped type,+  which can be verbose. Type aliases help at use sites but cannot simplify error+  output.++- **Orphan-instance risk.** If a user needs an instance that this library did+  not provide, they cannot add it without creating an orphan (since both the+  class and the type are defined elsewhere relative to the user). In practice+  this is mitigated by providing instances densely — if a lawful instance+  exists, this module should already have it.++### Performance implications++All three newtypes are strict newtypes — they vanish at runtime under GHC's+optimiser. The key mechanisms:++- **Newtype coercion.** GHC represents `Kleisli (->) a f b` identically to+  `a -> f b` in memory. Wrapping, unwrapping, and converting between the three+  newtypes via the provided isos are zero-cost (they compile to `id`/`coerce`+  after optimisation).++- **INLINE pragmas.** Every top-level combinator and every hand-written instance+  method carries an `INLINE` pragma, ensuring that after inlining the only code+  that remains is the user's function and the functor operations — no dictionary+  passing, no wrapper allocation.++- **DerivingVia and specialisation.** Instances derived via `Star`, `ReaderT`,+  or `Arrow.Kleisli` are thin wrappers around those types' methods. With+  optimisation enabled (`-O1` or above), GHC specialises and inlines through+  the via-type, eliminating it entirely. The generated Core is the same as if+  the instance had been written by hand.++- **No runtime indirection.** Because there is no existential, no GADT, and no+  dictionary field stored in the type, using these newtypes has exactly the same+  runtime cost as using the underlying `p a (f b)` directly. The abstraction is+  purely a compile-time mechanism for selecting instances.++In summary: you pay nothing at runtime for the newtype layer. The only cost is+at compile time — more instances means more constraint solving — but in practice+this is negligible.+ ## Building  ```
changelog.md view
@@ -1,3 +1,8 @@+0.0.3++* Add `mkKleisli'`, `mkProKleisli'`, `mkContraKleisli'` for constructing+  Identity-wrapped Kleisli values from plain profunctor values+ 0.0.2  * Add `pureKleisli`, `pureProKleisli`, `pureContraKleisli` for lifting
kleisli.cabal view
@@ -1,6 +1,6 @@ cabal-version:        2.4 name:                 kleisli-version:              0.0.2+version:              0.0.3 synopsis:             Kleisli-like newtypes with different type parameter orderings description:   Three newtype wrappers around @p a (f b)@ with different type parameter
src/Data/Kleisli.hs view
@@ -46,16 +46,19 @@      -- * Kleisli isomorphisms     kleisli',+    mkKleisli',     _Kleisli_ProKleisli,     _Kleisli_ContraKleisli,      -- * ProKleisli isomorphisms     proKleisli',+    mkProKleisli',     _ProKleisli_Kleisli,     _ProKleisli_ContraKleisli,      -- * ContraKleisli isomorphisms     contraKleisli',+    mkContraKleisli',     _ContraKleisli_ProKleisli,     _ContraKleisli_Kleisli, @@ -79,7 +82,7 @@ import Control.Comonad (Comonad (..), ComonadApply ((<@>))) import Control.Comonad.Traced.Class (ComonadTraced (..)) import Control.DeepSeq (NFData (..))-import Control.Lens (Iso, Rewrapped, Wrapped (..), iso)+import Control.Lens hiding (Traversing, (<.>)) import Control.Monad (MonadPlus) import Control.Monad.Cont.Class (MonadCont) import Control.Monad.Error.Class (MonadError)@@ -97,18 +100,17 @@ import Data.Functor.Apply (Apply (..)) import Data.Functor.Bind (Bind (..)) import Data.Functor.Bind.Trans (BindTrans)-import Data.Functor.Contravariant (Contravariant (..), Op (..))+import Data.Functor.Contravariant (Op (..)) import Data.Functor.Contravariant.Conclude (Conclude) import Data.Functor.Contravariant.Decide (Decide (..)) import Data.Functor.Contravariant.Divise (Divise (..)) import Data.Functor.Contravariant.Divisible (Decidable, Divisible (..)) import qualified Data.Functor.Contravariant.Rep as CRep (Representable (..)) import Data.Functor.Extend (Extend (..))-import Data.Functor.Identity (Identity (..)) import Data.Functor.Plus (Plus (..)) import qualified Data.Functor.Rep as FRep (Rep, Representable (..))-import Data.Profunctor (Closed, Profunctor (..), Strong)-import Data.Profunctor.Choice (Choice, Cochoice)+import Data.Profunctor (Closed, Strong)+import Data.Profunctor.Choice (Cochoice) import Data.Profunctor.Mapping (Mapping) import qualified Data.Profunctor.Rep as PRep (Representable (..)) import Data.Profunctor.Sieve (Sieve (..))@@ -229,6 +231,15 @@ kleisli' = iso (\(Kleisli x) -> rmap runIdentity x) (Kleisli . rmap Identity) {-# INLINE kleisli' #-} +-- | Construct a 'Kleisli'' from a plain profunctor value, wrapping the+-- result in 'Identity'.+--+-- >>> let Kleisli f = mkKleisli' (+1) :: Kleisli' (->) Int Int in runIdentity (f 5)+-- 6+mkKleisli' :: (Profunctor p) => p a b -> Kleisli' p a b+mkKleisli' = review kleisli'+{-# INLINE mkKleisli' #-}+ -- | An isomorphism between 'Kleisli' and 'ProKleisli', reordering type parameters. -- -- >>> let ProKleisli f = view _Kleisli_ProKleisli (Kleisli Just :: Kleisli (->) Int Maybe Int) in f 5@@ -256,6 +267,15 @@ proKleisli' = iso (\(ProKleisli x) -> rmap runIdentity x) (ProKleisli . rmap Identity) {-# INLINE proKleisli' #-} +-- | Construct a 'ProKleisli'' from a plain profunctor value, wrapping the+-- result in 'Identity'.+--+-- >>> let ProKleisli f = mkProKleisli' (+1) :: ProKleisli' (->) Int Int in runIdentity (f 5)+-- 6+mkProKleisli' :: (Profunctor p) => p a b -> ProKleisli' p a b+mkProKleisli' = review proKleisli'+{-# INLINE mkProKleisli' #-}+ -- | An isomorphism between 'ProKleisli' and 'Kleisli', reordering type parameters. -- -- >>> let Kleisli f = view _ProKleisli_Kleisli (ProKleisli Just :: ProKleisli (->) Maybe Int Int) in f 5@@ -282,6 +302,15 @@ contraKleisli' :: (Profunctor p, Profunctor p') => Iso (ContraKleisli' p b a) (ContraKleisli' p' b' a') (p a b) (p' a' b') contraKleisli' = iso (\(ContraKleisli x) -> rmap runIdentity x) (ContraKleisli . rmap Identity) {-# INLINE contraKleisli' #-}++-- | Construct a 'ContraKleisli'' from a plain profunctor value, wrapping the+-- result in 'Identity'.+--+-- >>> let ContraKleisli f = mkContraKleisli' (+1) :: ContraKleisli' (->) Int Int in runIdentity (f 5)+-- 6+mkContraKleisli' :: (Profunctor p) => p a b -> ContraKleisli' p b a+mkContraKleisli' = review contraKleisli'+{-# INLINE mkContraKleisli' #-}  -- | An isomorphism between 'ContraKleisli' and 'ProKleisli', reordering type parameters. --