capability-0.4.0.0: Extensional capabilities and deriving combinators
Safe HaskellNone
LanguageHaskell2010

Capability.Writer

Description

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.

Synopsis

Relational capability

class (Monoid w, Monad m, HasSink tag w m) => HasWriter (tag :: k) (w :: Type) (m :: Type -> Type) | tag m -> w where Source #

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.

listen @t (pure a) = pure (a, mempty)
listen @t (tell @t w) = tell @t w >> pure (w, w)
listen @t (m >>= k) = listen @t m >>= \(a, w1) -> listen @t (k a) >>= \(b, w2) -> pure (b, w1 `mappend` w2)
pass @t (tell @t w >> pure (a, f)) = tell @t (f w) >> pure a
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 Source #

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) Source #

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 Source #

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.

Instances

Instances details
(Monoid w, HasState tag w m) => HasWriter (tag :: k) w (WriterLog m) Source # 
Instance details

Defined in Capability.Writer

Methods

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

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

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

(Monoid w, Monad m, Reifies s (Reified tag (HasWriter tag w) m)) => HasSink (tag :: k) w (Reflected s (HasWriter tag w) m) Source # 
Instance details

Defined in Capability.Writer

Methods

yield_ :: Proxy# tag -> w -> Reflected s (HasWriter tag w) m () Source #

(Monad m, Monoid w, Reifies s (Reified tag (HasWriter tag w) m)) => HasWriter (tag :: k) w (Reflected s (HasWriter tag w) m) Source # 
Instance details

Defined in Capability.Writer

Methods

writer_ :: Proxy# tag -> (a, w) -> Reflected s (HasWriter tag w) m a Source #

listen_ :: Proxy# tag -> Reflected s (HasWriter tag w) m a -> Reflected s (HasWriter tag w) m (a, w) Source #

pass_ :: Proxy# tag -> Reflected s (HasWriter tag w) m (a, w -> w) -> Reflected s (HasWriter tag w) m a Source #

(forall x. Coercible (m x) (t2 (t1 m) x), Monad m, HasWriter tag w (t2 (t1 m))) => HasWriter (tag :: k) w ((t2 :.: t1) m) Source #

Compose two accessors.

Instance details

Defined in Capability.Writer

Methods

writer_ :: Proxy# tag -> (a, w) -> (t2 :.: t1) m a Source #

listen_ :: Proxy# tag -> (t2 :.: t1) m a -> (t2 :.: t1) m (a, w) Source #

pass_ :: Proxy# tag -> (t2 :.: t1) m (a, w -> w) -> (t2 :.: t1) m a Source #

(HasWriter tag w m, MonadTransUnlift t, Monad (t m)) => HasWriter (tag :: Type) w (Lift (t m)) Source #

Lift one layer in a monad transformer stack.

Note, that if the HasWriter instance is based on HasState, then it is more efficient to apply Lift to the underlying state capability. E.g. you should favour

deriving (HasWriter tag w) via
  WriterLog (Lift (SomeTrans (MonadState SomeStateMonad)))

over

deriving (HasWriter tag w) via
  Lift (SomeTrans (WriterLog (MonadState SomeStateMonad)))
Instance details

Defined in Capability.Writer.Discouraged

Methods

writer_ :: Proxy# tag -> (a, w) -> Lift (t m) a Source #

listen_ :: Proxy# tag -> Lift (t m) a -> Lift (t m) (a, w) Source #

pass_ :: Proxy# tag -> Lift (t m) (a, w -> w) -> Lift (t m) a Source #

data Reified (tag :: k) (HasWriter tag w) m Source # 
Instance details

Defined in Capability.Writer

data Reified (tag :: k) (HasWriter tag w) m = ReifiedWriter {}

writer :: forall tag w m a. HasWriter tag w m => (a, w) -> m a Source #

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.

tell :: forall tag w m. HasWriter tag w m => w -> m () Source #

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

listen :: forall tag w m a. HasWriter tag w m => m a -> m (a, w) Source #

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.

pass :: forall tag w m a. HasWriter tag w m => m (a, w -> w) -> m a Source #

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.

Functional capability

type HasWriter' (tag :: k) = HasWriter tag (TypeOf k tag) Source #

Type synonym using the TypeOf type family to specify HasWriter constraints without having to specify the type associated to a tag.

type family TypeOf k (s :: k) :: Type Source #

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 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

newtype SinkLog m (a :: Type) Source #

Accumulate sunk values with their own monoid.

Constructors

SinkLog (m a) 

Instances

Instances details
(Monoid w, HasState tag w m) => HasSink (tag :: k) w (SinkLog m) Source # 
Instance details

Defined in Capability.Sink.Internal.Strategies

Methods

yield_ :: Proxy# tag -> w -> SinkLog m () Source #

(Monoid w, HasState tag w m) => HasWriter (tag :: k) w (WriterLog m) Source # 
Instance details

Defined in Capability.Writer

Methods

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

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

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

Monad m => Monad (SinkLog m) Source # 
Instance details

Defined in Capability.Sink.Internal.Strategies

Methods

(>>=) :: SinkLog m a -> (a -> SinkLog m b) -> SinkLog m b #

(>>) :: SinkLog m a -> SinkLog m b -> SinkLog m b #

return :: a -> SinkLog m a #

Functor m => Functor (SinkLog m) Source # 
Instance details

Defined in Capability.Sink.Internal.Strategies

Methods

fmap :: (a -> b) -> SinkLog m a -> SinkLog m b #

(<$) :: a -> SinkLog m b -> SinkLog m a #

Applicative m => Applicative (SinkLog m) Source # 
Instance details

Defined in Capability.Sink.Internal.Strategies

Methods

pure :: a -> SinkLog m a #

(<*>) :: SinkLog m (a -> b) -> SinkLog m a -> SinkLog m b #

liftA2 :: (a -> b -> c) -> SinkLog m a -> SinkLog m b -> SinkLog m c #

(*>) :: SinkLog m a -> SinkLog m b -> SinkLog m b #

(<*) :: SinkLog m a -> SinkLog m b -> SinkLog m a #

MonadIO m => MonadIO (SinkLog m) Source # 
Instance details

Defined in Capability.Sink.Internal.Strategies

Methods

liftIO :: IO a -> SinkLog m a #

PrimMonad m => PrimMonad (SinkLog m) Source # 
Instance details

Defined in Capability.Sink.Internal.Strategies

Associated Types

type PrimState (SinkLog m) #

Methods

primitive :: (State# (PrimState (SinkLog m)) -> (# State# (PrimState (SinkLog m)), a #)) -> SinkLog m a #

type PrimState (SinkLog m) Source # 
Instance details

Defined in Capability.Sink.Internal.Strategies

Modifiers

Reflection

data family Reified (tag :: k) (c :: Capability) (m :: Type -> Type) Source #

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 HasSink capability has the method yield :: a -> m (). The corresponding dictionary type is defined as follows.

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

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

>>> :{
  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
      }
  :}

Instances

Instances details
data Reified (tag :: k) (HasSink tag a) m Source # 
Instance details

Defined in Capability.Sink.Internal.Class

data Reified (tag :: k) (HasSink tag a) m = ReifiedSink {}
data Reified (tag :: k) (HasSource tag a) m Source # 
Instance details

Defined in Capability.Source.Internal.Class

data Reified (tag :: k) (HasSource tag a) m = ReifiedSource {}
data Reified (tag :: k) (HasReader tag r) m Source # 
Instance details

Defined in Capability.Reader.Internal.Class

data Reified (tag :: k) (HasReader tag r) m = ReifiedReader {}
data Reified (tag :: k) (HasState tag s) m Source # 
Instance details

Defined in Capability.State.Internal.Class

data Reified (tag :: k) (HasState tag s) m = ReifiedState {}
data Reified (tag :: k) (HasThrow tag e) m Source # 
Instance details

Defined in Capability.Error

data Reified (tag :: k) (HasThrow tag e) m = ReifiedThrow {}
data Reified (tag :: k) (HasCatch tag e) m Source # 
Instance details

Defined in Capability.Error

data Reified (tag :: k) (HasCatch tag e) m = ReifiedCatch {}
data Reified (tag :: k) (HasWriter tag w) m Source # 
Instance details

Defined in Capability.Writer

data Reified (tag :: k) (HasWriter tag w) m = ReifiedWriter {}