HORIZON HASKELLDocslts/ghc-9.14.xd82dee82026-10-12Search names, modules, packages, or :: a typeCtrl K

GHC 9.14.1 · lts/ghc-9.14.x · d82dee8 · 2026-10-12

Modulecapability-0.5.0.1Haskell2010

Capability.Writer

Defines a capability type class for writer effects. A writer program can output values with tell. The values output by two consecutive sub-computation are combined using a monoid's mappend.

The interface of HasWriter follows that of MonadWriter. However, this module does not include a strategy to provide a HasWriter capability from a MonadWriter instance. It is generally a bad idea to use monads such as WriterT, as they tend to leak space, as described in this <https://blog.infinitenegativeutility.com/2016/7/writer-monads-and-space-leaks blog post> by Getty Ritter.

Instead, you should use the WriterLog strategy that implements the writer monad on a state monad. There is no downside, as using HasWriter instead of HasState directly ensures your code adheres to the writer monad interface and does not misuse the underlying state monad.

  • 4 types
  • 1 class
  • 14 values

Relational capability

6 declarations
classclass (Monoid w, Monad m, HasSink tag w m) => HasWriter (tag :: k) w (m :: Type -> Type) | tag m -> w where
#

Writer capability

An instance should fulfill the following laws. At this point these laws are not definitive, see https://github.com/haskell/mtl/issues/5.

Property
listen @t (pure a) = pure (a, mempty)
Property
listen @t (tell @t w) = tell @t w >> pure (w, w)
Property
listen @t (m >>= k) = listen @t m >>= \(a, w1) -> listen @t (k a) >>= \(b, w2) -> pure (b, w1 `mappend` w2)
Property
pass @t (tell @t w >> pure (a, f)) = tell @t (f w) >> pure a
Property
writer @t (a, w) = tell @t w >> pure a

A note on the HasSink super class.

HasSink offers one yield method with the same signature as tell. Many people's intuition, however, wouldn't connect the two: yielding tosses the value down some black-box chute, while telling grows and accumulation via the monoid. The connection is since the chute is opaque, the tosser cannot rule out there being such an accumulation at the chutes other end.

Formally, we reach the same conclusion. HasSink has no laws, indicating the user can make no assumptions beyond the signature of yield. HasWriter, with tell defined as yield, is thus always compatable regardless of whatever additional methods it provides and laws by which it abides.

Methods

  • writer_ :: Proxy# tag -> (a, w) -> m a

    For technical reasons, this method needs an extra proxy argument. You only need it if you are defining new instances of HasReader. Otherwise, you will want to use writer. See writer for more documentation.

  • listen_ :: Proxy# tag -> m a -> m (a, w)

    For technical reasons, this method needs an extra proxy argument. You only need it if you are defining new instances of HasReader. Otherwise, you will want to use listen. See listen for more documentation.

  • pass_ :: Proxy# tag -> m (a, w -> w) -> m a

    For technical reasons, this method needs an extra proxy argument. You only need it if you are defining new instances of HasReader. Otherwise, you will want to use pass. See pass for more documentation.

Instances5HasSink, HasWriter, Reified
valuewriter :: HasWriter tag w m => (a, w) -> m a
#

writer @tag (a, w) lifts a pure writer action (a, w) to a monadic action in an arbitrary monad m with capability HasWriter.

Appends w to the output of the writer capability tag and returns the value a.

valuetell :: HasWriter tag w m => w -> m ()
#

tell @tag w appends w to the output of the writer capability tag.

valuelisten :: HasWriter tag w m => m a -> m (a, w)
#

listen @tag m executes the action m and returns the output of m in the writer capability tag along with result of m. Appends the output of m to the output of the writer capability tag.

valuepass :: HasWriter tag w m => m (a, w -> w) -> m a
#

pass @tag m executes the action m. Assuming m returns (a, f) and appends w to the output of the writer capability tag. pass @tag m instead appends w' = f w to the output and returns a.

valuecensor :: HasWriter tag w m => (w -> w) -> m a -> m a
#

censor @tag f m is an action that executes the action m and applies the function f to the output of the writer capability tag, leaving the return value unchanged.

Functional capability

2 declarations
familytype family TypeOf k (s :: k)
#

Type family associating a tag to the corresponding type. It is intended to simplify constraint declarations, by removing the need to redundantly specify the type associated to a tag.

It is poly-kinded, which allows users to define their own kind of tags. Standard haskell types can also be used as tags by specifying the Type kind when defining the type family instance.

Defining TypeOf instances for GHC.TypeLits.Symbols (typelevel string literals) is discouraged. Since symbols all belong to the same global namespace, such instances could conflict with others defined in external libraries. More generally, as for typeclasses, TypeOf instances should always be defined in the same module as the tag type to prevent issues due to orphan instances.

Example:

    import Capability.Reader

    data Foo
    data Bar
    type instance TypeOf Type Foo = Int
    type instance TypeOf Type Bar = String

    -- Same as: foo :: HasReader Foo Int M => …
    foo :: HasReader' Foo m => …
    foo = …

Strategies

3 declarations
newtypenewtype SinkLog (m :: Type -> Type) a
#

Accumulate sunk values with their own monoid.

Constructors

Instances8HasSink, HasWriter, Monad, Functor, Applicative, MonadIO, …

Modifiers

Reflection

14 declarations
data familydata family Reified (tag :: k) (c :: Capability) (m :: Type -> Type)
#
Reified tag capability m

Defines the dictionary type for the methods of capability under tag in the monad m. Refer to interpret_ for an example use-case.

For example, the Capability.Sink.HasSink capability has the method Capability.Sink.yield :: a -> m (). The corresponding dictionary type is defined as follows.

Example1 expression
:{  data instance Reified tag (HasSink tag a) m =    ReifiedSink { _yield :: forall a. a -> m () }  :}

Superclass dictionaries are represented as nested records. For example, the Capability.State.HasState capability has the superclasses Capability.Source.HasSource and Capability.Sink.HasSink and the method Capability.State.state :: (s -> (a, s)) -> m a. The corresponding dictionary type is defined as follows.

Example1 expression
:{  data instance Reified tag (HasState tag s) m =    ReifiedState      { _stateSource :: Reified tag (HasSource tag s) m,        _stateSink :: Reified tag (HasSink tag s) m,        _state :: forall a. (s -> (a, s)) -> m a      }  :}
Instances7Reified, …
value_state :: Reified tag (HasState tag s) m -> forall a. (s -> (a, s)) -> m a
#