In composite StateT / Maybe monad, how to take either possibility that succeeds?
Asked Answered
R

1

6

In Haskell, here's a monad that combines the State and Maybe monads:

type StatefulMaybe a = StateT Int Maybe a

This is a computation that can succeed (returning a value) or fail. If it succeeds, it carries a state along with the returned value.

I'd like to write a function

choice :: StatefulMaybe a -> StatefulMaybe a -> StatefulMaybe a

that takes two such computations and returns the first one (if any) that succeeds. Only state changes by the successful computation are carried forward.

In fact after some experimentation I figured out how to write this. Here it is:

orMaybe :: Maybe a -> Maybe a -> Maybe a
orMaybe (Just x) _ = Just x
orMaybe Nothing x = x

choice :: StatefulMaybe a -> StatefulMaybe a -> StatefulMaybe a
choice mx my = StateT (\s ->
    (runStateT mx s) `orMaybe` (runStateT my s)
  )

It works:

foo :: StatefulMaybe String
foo = do
    modify (+ 20)
    fail "didn't succeed"

baz :: StatefulMaybe String
baz = do
    modify (+ 30)
    return "two"

bar :: StatefulMaybe String
bar = do
    s <- choice foo baz
    return (s ++ " done")

> runStateT bar 0
Just ("two done",30)

My question is this: Is there some easier or more natural way to write this choice function than my implementation above? In particular, is there some way I can lift the orMaybe function into my monad?

Revenge answered 7/4, 2021 at 19:26 Comment(1)
orMaybe is mplus from MonadPlus -- I think using mplus instead of choice should work, too.Boeke
P
5

If I understand the question correctly, I think that this function already exists: <|>:

bar :: StatefulMaybe String
bar = do
    s <- foo <|> baz
    return (s ++ " done")

The <|> function is part of the Applicative type class, of which StateT is an instance when the inner monad is both a Functor and MonadPlus instance, which is true for Maybe.

*Q66992923> runStateT bar 0
Just ("two done",30)

As luqui suggests in the comment, I think that mplus ought to work, too, since the default implementation of mplus is <|>.

Poky answered 7/4, 2021 at 19:59 Comment(1)
Fantastic. I just knew there had to be an easier way. :)Revenge

© 2022 - 2024 — McMap. All rights reserved.