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?
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…
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.
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.
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.
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)
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