Operate on a value inside a Maybe without "unwrapping"/"re-wrapping"

Given a function signature like:

thing :: Maybe (a -> b) -> a -> Maybe b

How can I apply the (a -> b) function to a without having to unwrap (to use the Rust term) and re-wrap it? I’ve come up with these solutions, which I believe are functionally identical, but it feels like there’s probably a cleaner way to do it without having to use Just/return/pure, etc., as well as propagating a Nothing value through it. This is certainly not a showstopper for me, I’m just trying to become more familiar and comfortable with the tools at my disposal for dealing with monads/applicative functors.

thing :: Maybe (a -> b) -> a -> Maybe b
thing (Just op) = Just . op

thing1 :: Maybe (a -> b) -> a -> Maybe b
thing1 maybeOp y = maybeOp >>= \op -> Just $ op y

thing2 :: Maybe (a -> b) -> a -> Maybe b
thing2 maybeOp = ap maybeOp . Just
2 Likes
thing mf a = fmap ($ a) mf  

or,

thing mf a = ($ a) <$> mf 

In other words, use that Maybe is a functor, and that you can apply a function using $.

Your first function is partial by the way; i.e. if you give it a Nothing your function will crash.

2 Likes

Ah, that’s perfect. I think it was the ($ a) that was eluding me – it’s like the function is implied to be on the left side of the $.

Thank you for the help.

Yes, it is “just” sectioning of the $ function/operator; i.e. $ has type (a → b) → a → b, so partially applying it/using sectioning (in the same way that you can write (+3) to mean the equivalent of \x → x + 3, you can write ($ x), if you have some x of type a.

1 Like

Pro tip: Hoogle lets you search for things like this by type!

https://hoogle.haskell.org/?hoogle=Maybe+(a+->+b)+->+a+->+Maybe+b

As you can see, a ‘standard’ name for this operator is flap, and you can click through to its source to see one possible definition.

2 Likes

That makes total sense now. I appreciate you taking the time to break it down for me.

I did do that, actually. Then I saw that it lead to some non-“standard” package and didn’t go any further. I should’ve realized that it would still make sense to look at the source. Thanks.

I’m not getting what you think you might be avoiding. There’s no notion of Haskell “re-wrapping” something and somehow re-using an updated location: always Haskell builds a new value/no sharing. And in your case, the return value is a different type, so definitely no attempt to re-anything.

The solutions you suggest to elide an argument, and those others have given are perhaps neater code (debatable), but they’re going to compile down (after eta-expansion) to the same:

thinge (Just op) y = Just $ op y

I think the other two (thing1, thing2) are also partial. They want to apply an explicit Just on RHS, so they need an explicit (Just f) on LHS.

The fmap solution, because it’s looking for a Functor will take Nothing to Nothing, but that’s a happy accident of wanting to take Maybe to Maybe.

1 Like

In addition to Operate on a value inside a Maybe without "unwrapping"/"re-wrapping" - #2 by noinia

thing1 and thing2 can also be written as

thing3 maybeOp x = maybeOp >>= \op -> return (op x) -- everything to the right of the ( >>= ) is our (a -> mb function); in this cause, our `a` is actually an `a -> b` so we need to apply it to an `a` before using pure/return to make it an `mb`.

And this will also help you with the Nothing case since:

( >>= ) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= _ = Nothing
Maybe x >>= f = f x

Also you have applicative here where you could do:

thing4 maybeOp x = maybeOp <*> pure x -- (pure x because we're lifting our normal value into the context it needs: in this case, a `Just x`).

since

(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
Nothing <*> _  = Nothing
_ <*> Nothing = Nothing
Just ab <*> Just a = Just (ab a)

Essentially, if you don’t want to unwrap values, you can use functor, applicative or monad to act on results.

Functor for when you just want to change a value in a context.

Applicative for when you want to combine many values or results together into a new structure.

Monad for when you want to take the result of a computation and then apply it to a function that then produces a new result or not.

For this reason, I would choose Functor in your case. The applicative function I gave is just the Homomorphism law for applicative.

2 Likes

Why didn’t it bring up the most desirable results, fmap and <$> ?

1 Like

Because it would have needed to synthesise a composition of multiple functions, like \x y -> fmap ($ y) x

There is a tool called hoogle+ that does this, but the online demo doesn’t seem to work any more.

2 Likes

Thank you for the reply. I’m not well-enough versed in Haskell to know exactly which terms to use and when, so my “unwrap/rewrap” reference was just my attempt at explaining what I’m trying to do using the Rust terminology I knew. Having to write Just in the pattern matching to get at the internal value only to write Just again to put the resulting value into a Maybe seemed redundant. I’m mostly just trying to expand my knowledge on the different ways to handle these situations and not necessarily, in this case, try to do what is the most practical or most pragmatic. This thread has helped on all fronts!

1 Like

Why would ‘thing1’ and ‘thing2’, defined as:

be partial? Both seem. to handle both the Just and the Nothing case perfectly fine:

ghci> thing1 Nothing 5 
Nothing
ghci> thing1 (Just succ) 5 
Just 6
ghci> thing2 (Just succ) 5 
Just 6
ghci> thing2 Nothing 5 
1 Like

Personally I’d just use do notation

thing :: Maybe (a -> b) -> a -> Maybe b
thing mf a = do
  f <- mf
  pure (f a)
1 Like

Ah, yes my mistake. The Maybe instance for Monad gives you Nothing -> Nothing for free.

1 Like