How to avoid superfluous variables in do notation?
Asked Answered
D

2

16

Say in a Haskell do-notation block, I wish to have a variable is_root indicating if I am root:

import System.Posix.User
main = do
    uid <- getRealUserID
    is_root <- return $ uid == 0

That annoying uid variable is only used in that one place. I wish instead I could write this:

main = do
    is_root <- getRealUserID == 0

But of course that won't compile.

How can I get rid of superfluous variables like uid? Here's the best I've come up with:

import System.Posix.User
main = do
    is_root <- getRealUserID >>= return . ((==) 0)

Blech! Is there a nicer way?

Diaghilev answered 14/8, 2014 at 18:11 Comment(2)
This doesn't get rid of the extra variable, but you should also be using let is_root = uid == 0 instead of is_root <- return $ uid == 0. It's not a monadic computation, so there's no need to wrap it in return and use bind.Gateway
I don't see anything ugly in your attempt, just in the right operand of bind it may be shorten to return . (== 0).Heiser
T
23

One way is

fmap (== 0) getRealUserID
Thoer answered 14/8, 2014 at 18:15 Comment(5)
Note that m >>= return . f (as in the code in the question) is required to be equal to fmap f m by the Monad laws: hackage.haskell.org/package/base/docs/…Kibitzer
You can also use the Applicative instance, something like: is_root <- (==0) <$> getRealUserID, which I think reads a little better.Interbreed
@PeterHall (<$>) is still a synonym for fmap from the Functor instance, it's just frequently used together with applicative notation (and re-exported from Control.Applicative for that purpose).Patristic
(<$>) is also in Data.FunctorScalise
As the computation runs in IO context for which (with proper imports) Functor, Applicative and of course Monad instances do exist, you may apply whatever mapping function as you please.Heiser
L
6

(I assume your goal is to limit the scope of uid, not to just be pointfree for its own sake)

In a simple case like this, @pdw's answer is probably the way to go. The operators <$> and <*> from Control.Applicative are particularly helpful here.

foo = do
  are_same <- (==) <$> doThis <*> doThat

In slightly more complicated situations, you could use a nested-do:

complicatedEq :: This -> That -> IO Bool

main = do
  are_same <- do
    this <- doThis
    that <- doThatBasedOn this
    complicatedEq this that
  ... rest ...

Anything significantly long probably deserves to be its own function.

Loveliesbleeding answered 14/8, 2014 at 18:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.