modular
modular

Reputation: 1099

Can we define a Monad instance for WrappedArrow?

WrappedArrow is an instance of Applicative, but can it be made a Monad (probably if the arrow is ArrowApply)?

instance ArrowApply a => Monad (WrappedArrow a b) where
  return = pure
  (>>) = (*>)
  (>>=) = ???

EDIT:

My initial purpose was to have a Monad instance for (wrapped) Kleisli, so I could write

runWithInput input $ do
  action1
  action2
  action3
  ...

instead of

do
  output1 <- action1 input
  output2 <- action2 output1
  output3 <- action3 output2
  ...

But I realized that won't make desirable sense. Stripped of newtypes, Kleisli f a b >> Kleisli f a c is

(a -> f b) -> (a -> f c) -> (a -> f c)

And what I need is analog of >>>, i.e. b instead of second a:

(a -> f b) -> (b -> f c) -> (a -> f c)

So I suppose I will have to use StateT or a custom monad if I want to sequence actions in this manner with do.

Upvotes: 2

Views: 103

Answers (2)

duplode
duplode

Reputation: 34378

WrappedArrow is an instance of Applicative, but can it be made a Monad (probably if the arrow is ArrowApply)?

I will put WrappedArrow aside for a moment and consider the subtly different question "Can we successfully implement instance ArrowApply y => Monad (y r)?". The answer to this question is "yes". One way of demonstrating it relies on the ArrowMonad newtype chi mentions...

newtype ArrowMonad a b = ArrowMonad (a () b)

... and the following isomorphism (cf. this cstheory.SE question and page 18 of Idioms are oblivious, arrows are meticulous, monads are promiscuous):

kleislify :: ArrowApply y => y r a -> (r -> y () a)
kleislify af = \r -> arr (const r) >>> af

unkleislify :: ArrowApply y => (r -> y () a) -> y r a
unkleislify f = arr f &&& arr (const ()) >>> app

-- unkleislify . kleislify = kleislify . unkleislify = id

ArrowMonad gives us a monad instance that we can use by kleislify-ing the arrows and supplying a common argument to the resulting functions (in other words, we are using the ArrowMonad bind through the applicative instance for functions):

bindY :: ArrowApply y => y r a -> (a -> y r b) -> y r b
bindY af h = unkleislify $ (\(ArrowMonad am) -> am) . (\r ->
    ArrowMonad (kleislify af r) >>= \x -> ArrowMonad (kleislify (h x) r))

The relevant return is also the ArrowMonad one, couched in the appropriate layers of boilerplate:

returnY :: ArrowApply y => a -> y r a
returnY x = unkleislify $ \r -> (\(ArrowMonad am) -> am) (return x)

This, however, does not answer your question. For that to happen, bindY and returnY should be consistent with the Applicative instance of WrappedArrow; that is, we should have returnY x = arr (const x), and the ap we can write with bindY and returnY should be equivalent to (<*>) for WrappedMonad. For instance, we might try using the definitions of the relevant ArrowMonad instances...

instance Arrow a => Applicative (ArrowMonad a) where
   pure x = ArrowMonad (arr (const x))
   ArrowMonad f <*> ArrowMonad x = ArrowMonad (f &&& x >>> arr (uncurry id))

instance ArrowApply a => Monad (ArrowMonad a) where
    ArrowMonad m >>= f = ArrowMonad $
        m >>> arr (\x -> let ArrowMonad h = f x in (h, ())) >>> app  

... to expand (and then hopefully simplify) returnY:

returnY
unkleislify $ \r -> (\(ArrowMonad am) -> am) (return x)
unkleislify $ \r -> (\(ArrowMonad am) -> am) (ArrowMonad (arr (const x)))
unkleislify $ \r -> arr (const x)
arr (\r -> arr (const x)) &&& arr (const ()) >>> app
arr (const (arr (const x))) &&& arr (const ()) >>> app
arr (\r -> (r, r)) >>> arr (const (arr (const x))) *** arr (const ()) >>> app
arr (\r -> (arr (const x), ())) >>> app

I have no idea whether that can be simplified to arr (const x) for any ArrowApply. Perhaps being able to flip arrows (as Alec and user2407038 suggest) would help getting rid of the (), but I haven't worked that out. In any case, for Kleisli at least we can carry on:

arr (\r -> (arr (const x), ())) >>> app
Kleisli (\r -> return (arr (const x), ())) >>> Kleisli (\(Kleisli f, x) -> f x)
Kleisli ((\r -> return (arr (const x), ())) >=> (\(Kleisli f, x) -> f x))
Kleisli ((\r -> return (Kleisli (return . const x), ()))
    >=> (\(Kleisli f, x) -> f x))
Kleisli (\r -> return (Kleisli (return . const x), ())
    >>= (\(Kleisli f, x) -> f x))
Kleisli (\r -> (\(Kleisli f, x) -> f x) (Kleisli (return . const x), ()))
Kleisli (\r -> (return . const x) ())
Kleisli (\r -> return x)
Kleisli (return . const x)
arr (const x)

I haven't attempted doing the same with bindY, but my uninformed guess is that a similar scenario would result.

Upvotes: 1

Alec
Alec

Reputation: 32319

Since this is an XY problem, I'll answer both the question you asked, and the question you probably wanted to ask:

WrappedArrow is an instance of Applicative, but can it be made a Monad (probably if the arrow is ArrowApply)?

Yes it can, but you'll need more constraints. In fact, I think there are several ways to do this. For example, you could take the approach suggested by @user2407038:

class Arrow a => ArrowSwap a where
  swap :: a b (a c d) -> a c (a b d)

instance (ArrowApply a, ArrowSwap a) => Monad (WrappedArrow a b) where
  return = pure
  WrapArrow m >>= f = WrapArrow $ swap (arr (unwrapArrow . f)) &&& m >>> app

You can get some intuition for ArrowSwap via its instance for (->):

instance ArrowSwap (->) where
  swap :: (b -> (c -> d)) -> c -> b -> d
  swap = flip

Of course, it isn't immediately clear that this is useful...

My initial purpose was to have a Monad instance for (wrapped) Kleisli, so I could write

runWithInput input $ do
  action1
  action2
  action3
  ...

instead of

do
  output1 <- action1 input
  output2 <- action2 output1
  output3 <- action3 output2
  ...

This is what RebindableSyntax is for:

 {-# LANGUAGE RebindableSyntax #-}

 import Control.Monad (>=>)

 action1 :: Monad m => T1 -> m T2
 action2 :: Monad m => T2 -> m T3
 action3 :: Monad m => T3 -> m T4

 action4 :: Monad m => T1 -> m T4
 action4 = let (>>) = (>=>) in do
   action1
   action2
   action3

Upvotes: 3

Related Questions