Is there an inverse operation to >>= (kinda)

Hello!

I’m learning and starting to notice a pattern I can’t quite put my finger on, it’s bugging me :slight_smile:

Let’s say Just represents the success of an operation, Nothing represents a failure.

So if I’ve got Nothing, I won’t go to step 1.
But if I passed step 0, then I can go to step 1.

I may or may not use x:

Prelude> Nothing >>= \x -> Just 1
Nothing
Prelude> Just 0 >>= \x -> Just 1
Just 1

But sometimes, I want to do the inverse. Nothing represents a success, and Just a failure.

So if I’ve got a Just, I won’t got to step 1.
And if I had Nothing at step 0, then I can go to step 1.

Maybe this operator would look like this: >>!

Using x doesn’t make any sense here (there is no x to be used or of value)

-- Wishfull thinking below
Prelude> Just 0 >>! \x -> Just 1
Nothing
Prelude> Nothing >>! \x -> Just 1
Just 1

Now of course it’s confusing to mix what Just and Nothing represent, and it may be a better idea to convert the types so that Just always represents the happy path. Or use better type names altogether.

But still, I’m wondering if, at a high level, what I’m describing exists and has a name?

2 Likes

I think the normal >>= operator with Either Int Unit behaves the way you want it to. This can represent a computation which either fails with an int or succeeds with no result.

2 Likes

Thanks for your feedback. If I understand things correctly, that would also fall into the category of “converting the type” I mentioned. Let’s imagine I did not choose the original type, and I don’t want to do this conversion.

I’m thinking more “abstractly”. The way I understand things, >>= constraints the flow of execution.

I’m looking for the same constraint of execution, but with an “inverse” logic.

It’s possible I’m simply misguided though.

2 Likes

I think you’re looking for <|>. It “fails” at the first Just

Just 1 <|> Just 2 = Just 1

And it skips over Nothing as if it was a success:

Nothing <|> Just 1 = Just 1

It works out if you consistently think about Just as failure and Nothing as success, even in the final result.

See also this post of mine where I use the alternative instance of Maybe in this way:

3 Likes

The closer I know to that is Control.Applicative.Alternative

> ghci
> import Control.Applicative
> Just 0 <|> Just 1
Just 0
> Nothing <|> Just 1
Just 1
> Just 0 <|> Nothing
Just 0
> Nothing <|> Nothing
Nothing

I don’t know any built-in operator which does Just x >>! Just y == Nothing… and honestly is kind of weird, but you can always define it!

(>>!) (Just _) (Just _) = Nothing
(>>!) (Just x) Nothing  = Just x
(>>!) Nothing  (Just x) = Just x
(>>!) _            _    = Nothing
2 Likes

Thanks!

<|> looks quite close, but in hindsight I think I made a mistake.

Because in my “reverse” case, I’m actually throwing away the equivalent x value so both functions would not really be equivalent.

Introducing an Either type works great but I’m still left with the feeling that I’m missing something…

No big deal though, keep on learning :slight_smile:

1 Like

Notice that this function doesn’t make too much sense. Following your example:

-- The type of this operator must be this (unless you are looking for something very polimorphic)
(>>!) :: Maybe a -> (a -> Maybe b) -> Maybe b

-- from your example, it must follow:
Nothing >>! (\x -> Just 1) == Just 1

-- But what would happen if the function I pass as a second argument do use its first argument? for example
Nothing >>! (\x -> Just x + 1) == Just ??? -- The only sensible thing is returning Nothing, but that's the monad instance you don't want. 

I’d suggest to write down the type you want to have and then try to implement a custom operator for such a type signature. I think you are triying to write something that can’t be written with Maybe

1 Like

I think what you want to achieve is too general and has too much to do with how we “understand” the Maybe monad. So if we say "Maybe represents a computation which can either fail or produce a result" we can flip this “intuitive representation” into "Maybe represents a computation which can either not produce any failures, or fail with a given error".

However there is no such general interpretation for monads, other than that they allow some form of sequencing of computations, and that next steps can depend on result of previous steps (which distinguishes them from Applicatives, for instance). Think State:

newtype State a = State (s -> (a, s))

What would your “inverted logic” mean in this example?

I think it touches a bit the “monad tutorial fallacy” problem: in the real world, monads are great, but each monad we use we learn by examples; each of them serves its own purpose. It’s kinda hard to come up with a general law/operation that works and can be applied to all types of monads - if it doesn’t follow from Monad laws, we deal with a totally different kind of object.

PS. Monads are like burritos.

2 Likes

Yes forgive me if I’m not making much sens.

This is roughly what I had in mind:

import Prelude hiding ((>>=))

parseNumOk :: Maybe Int
parseNumOk = Just 1

parseNumFail :: Maybe Int
parseNumFail = Nothing

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
ma >>= f = 
    case ma of
        Nothing -> Nothing
        Just a -> f a

(>>!) :: Maybe a -> Maybe b -> Maybe b
ma >>! mb =
    case ma of
        Nothing -> mb
        Just _ -> Nothing

forFurtherComputation =
    parseNumOk >>= \n ->
        Just n

forErrMsg =
    parseNumFail >>! Just "Error: bad input"

In both cases I want to wrap the final type, but for different purposes.

*Utils> forFurtherComputation 
Just 1
*Utils> forErrMsg 
Just "Error: bad input"

I’ve noticed this need to (kinda) “invert” the logic of a typical monad construct (from the same source of data), and was wondering if there was something to it.

I’m aware my demonstration is clunky though, an Either would combine those two conditions nicely.

NOTE: I removed the useless parameter for clarity (thus the signatures differ slightly)

1 Like

That it does:

module TestMe where
import Prelude hiding (return, (>>=), fail)

 -- a convenient type synonym
type Parse a = Either String a

 -- make your very own errant parser
fail :: String -> Parse a
fail = Left

 -- The dynamic monadic duo!
return :: a -> Parse a
return = Right

(>>=) :: Parse a -> (a -> Parse b) -> Parse b
ma >>= f = 
    case ma of
        Left msg -> Left msg
        Right x -> f x

 -- ...and the "test subject"
(>>!) :: Parse a -> Parse b -> Parse b
ma >>! mb =
    case ma of
        Left _  -> mb
        Right _ -> Left "???"

 -- those examples
parseNumOk :: Parse Int
parseNumOk = return 1

parseNumFail :: Parse Int
parseNumFail = fail "cannot parse number"

forFurtherComputation =
    parseNumOk >>= \n ->
        return n

forErrMsg =
    parseNumFail >>! fail "Error: bad input"

…no: I still can’t see the point of (>>!) - do you have any more examples?


1 Like