[Solved] Using `whenJust`

Stack’s code makes a lot of use of base's when :: Applicative f => Bool -> f () -> f (). I wondered if there was a Applicative f => Maybe a -> (a -> f()) -> f () and Hoogle found whenJust from extra (already a dependency of Stack). So:

case mSomething of
  Nothing -> pure ()
  Just value -> do
    complicatedActionWith value

can become:

whenJust mSomething $ \value -> do
  complicatedActionWith value

Which, for me, is a more expressive syntax. My question is: Is there any downside with replacing the former form with the latter form? The former form appears a lot in Stack’s code.

EDIT: I suppose what I am really asking is: Can I safely assume that the compiler does not care, one way or the other?

2 Likes

I wondered if there was a Applicative f => Maybe a -> (a -> f()) -> f ()

The answer is always traverse.

Well, flip traverse_ = for_ in this case.

10 Likes

The Haddock documentation for whenJust does say it is a specialised for_.

2 Likes

hah the other day I wanted a function :: Monoid a => Maybe a -> a; fromMaybe mempty

my work’s internal hoogle’s first result?

fold :laughing: love it

...or:
do value <- whenJust mSomething pure
   complicatedActionWith value

As @tdammers noted here, it will depend on what the previous programmers (and now yourself) consider to be important: in some places using the case-expression could be more informative; in other places: not so much. But I would tend to leave alone those places where detailed “walk-though” documentation exists - the writer of that documentation (and possibly the associated code) would have chosen that more verbose style for a reason…

2 Likes

If by “the compiler does not care” you mean that it is able to optimise the latter to the former, then yes, the compiler does not care

1 Like

I still would prefer whenJust over for_ here. It’s just more readable, it gives you more context. The same way I prefer map over fmap when I know I’m working with lists.

7 Likes

For for_ (and other Foldable-related functions like length) I tend to use for_ @Maybe instead of whenJust. That gives a bit of context without having to remember the name of the specialized function.

5 Likes

Me too. I have so many traverse_/fold/foldMaps written by a previous author on my current codebase at work, and it’s always very headscratching to understand what they mean.

5 Likes

I feel like much crisis could be averted by having HLS inline the definition for you (and then you undo it).

Not an option for code review, though.

The Agda code base also uses whenJust (rather than for_), to improve readability.
We use whenJust a lot!

3 Likes

I’m confused by this example. Wouldn’t value <- whenJust mSomething pure always return unit? value cant have a value if mSomething is Nothing

Hmm:

whenJust mg k = maybe (pure ()) k mg

              = case mg of Just x  -> k x
                           Nothing -> pure ()

…now to look at when:

when p s  = if p then s else pure ()
 -- or --
          = if p then k () else pure ()  {- if s = k () -}

Since in this case the continuation isn’t always used - yes, you’re correct; my “redundant continuation” heuristic doesn’t actually apply (for a change!).

You could write something like what you wrote if you sprinkle in the MaybeT transformer and an appropriate “handler”:

whenMaybeT :: Alternative f => MaybeT f () -> f ()
whenMaybeT (MaybeT m) = m >>= maybe empty pure

...

  whenMaybeT $ do value <- hoistMaybe mSomething
                  lift (complicatedActionWith value)

I guess Lean 4’s new do notation sugar could support something like that more elegantly.

No need for do notation sugar. One test of a good effect system is whether it supports this use case cleanly. Here’s what it looks like in my, unreleased, effect system, Bluefin:

withJump $ \j -> do
    value <- unwrapOrJumpTo j mSomething
    complicatedActionWith value
2 Likes

Many thanks for the advice. I decided to make (and have now made) greater use of whenJust in Stack’s code.

1 Like