Reputation: 1099
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
Reputation: 34378
WrappedArrow
is an instance ofApplicative
, but can it be made aMonad
(probably if the arrow isArrowApply
)?
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
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 ofApplicative
, but can it be made aMonad
(probably if the arrow isArrowApply
)?
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