Collect values at beginning of monad

Came across a pattern today, curious if anyone knows if it’s used anywhere today:

data CollectM w m a
  = CollectPure w a
  | CollectM w (m a)

instance Functor m => Functor (CollectM w m) where
  fmap f = \case
    CollectPure w a -> CollectPure w (f a)
    CollectM w m -> CollectM w (f <$> m)

instance (Monoid w, Monad m) => Applicative (CollectM w m) where
  pure = CollectPure mempty
  (<*>) = ap

instance (Monoid w, Monad m) => Monad (CollectM w m) where
  CollectPure w1 a >>= k =
    case k a of
      CollectPure w2 b -> CollectPure (w1 <> w2) b
      CollectM _ mb -> CollectM w1 mb
  CollectM w ma >>= k = CollectM w $ do
    a <- ma
    case k a of
      CollectPure _ b -> pure b
      CollectM _ mb -> mb

The general idea is all CollectPure’s at the beginning merge, then the collected monoid is propagated read-only to the rest of the compution.

1 Like

I don’t think it’s a monad, is it? Consider

CollectPure w1 () >> (CollectPure w2 () >> CollectM w3 (pure ())
  == CollectPure w1 () >> (CollectM w2 (pure ())
  == CollectM w1 (pure ())

whereas

(CollectPure w1 () >> CollectPure w2 ()) >> CollectM w3 (pure ())
  == CollectPure (w1 <> w2) >> CollectM w3 (pure ())
  == CollectM (w1 <> w2) (pure ())
2 Likes

ah right, what if you always merge the monoid on CollectPure?

instance (Monoid w, Monad m) => Monad (CollectM w m) where
  CollectPure w1 a >>= k =
    case k a of
      CollectPure w2 b -> CollectPure (w1 <> w2) b
      CollectM w2 mb -> CollectM (w1 <> w2) mb
  CollectM w ma >>= k = CollectM w $ do
    a <- ma
    case k a of
      CollectPure _ b -> pure b
      CollectM _ mb -> mb
1 Like

That looks more likely to be a monad, but I’m not sure it any longer does what you initally wanted.

It should for my purposes, but perhaps my initial description is no longer accurate.

Ultimately, I want

getCfg :: CollectM Cfg m a -> Cfg
getCfg = \case
  CollectPure cfg _ -> cfg
  CollectM cfg _ -> cfg

to return the merged configs from the longest prefix of CollectPure’s. I think this does that?

Yes I think so (plus the config from the first CollectM).