3
\$\begingroup\$

The below code works fine but I have the feeling that there must be something more idiomatic way to do this within the do-notation without having to resort to the two liftMs?

main = do
  n <- (liftM read (liftM head getArgs)) :: IO Int
  putStrLN . show $ sum [1..n]

If I do:

main = do
  args <- getArgs
  arg1 <- head args
  n <- read arg1 :: IO Int
  putStrLn . show $ sum [1..n]

I get:

Expected type: String
  Actual type: Char
In the first Argument of 'read', namely 'arg1'

which I do not understand. <- getArgs returns IO [String], so <- head args should return IO String, but it seems to return IO Char?! (as per error message).

So my two questions are:

  1. what is the most idiomatic way do do this
  2. what's wrong in the second version?
\$\endgroup\$
1
  • 1
    \$\begingroup\$ main = do [n] <- getArgs; print $ sum [1..read n] \$\endgroup\$
    – Gurkenglas
    Commented Nov 24, 2017 at 21:29

2 Answers 2

2
\$\begingroup\$

I have the Feeling that there must be something more idiomatic way to do this within the do-notation withouth having to resort to the two liftMs?

Yes, a single one. liftM is fmap nowadays. It stems from a time when Functor wasn't a superclass for Monad. The Functor laws state that

fmap f . fmap g = fmap (f . g)

Therefore, we can simplify

liftM read (liftM head getArgs)

to

fmap (read . head) getArgs

We end up with

main = do
  n <- fmap (read . head) getArgs :: IO Int
  putStrLN . show $ sum [1..n]

Also, putStrLn . show is print. If we introduce a small helper function, the code gets more readable:

readFirstElement :: Read a => [String] -> a 
readFirstElement = read . head

sumNaturalUpTo :: Int -> Int
sumNaturalUpTo n = sum [1..n]

main :: IO ()
main = do
  n <- readFirstElement <$> getArgs
  print $ sumNaturalUpTo n

Note that sum [1..n] is n * (n + 1) `div` 2, though, so we can optimize sumNaturalUpTo to

sumNaturalUpTo n = n * (n + 1) `div` 2

Alternatively, we can write

main :: IO ()
main = do
   args <- getArgs
   print $ sumNaturalUpTo $ readFirstElement args

Which might be the easiest one to read.

what's wrong in the second version?

args is not a IO … value.

\$\endgroup\$
2
  • \$\begingroup\$ So (<$>) = fmap? \$\endgroup\$
    – A Sz
    Commented Nov 24, 2017 at 20:17
  • \$\begingroup\$ @ASz Right. I'm sure I had that in a draft., sorry. \$\endgroup\$
    – Zeta
    Commented Nov 25, 2017 at 8:28
4
\$\begingroup\$

This is the code you're looking for:

main = do
  args <- getArgs
  let arg1 = head args
  let n = (read arg1) :: Int
  putStrLn . show $ sum [1..n]

Think of the <- operator as "extract". It "pulls" a value out of a monad.

But once you've pulled args out of the IO monad, you've now got a pure list of strings. It's not in the IO monad, so you can't extract from it. let instead just does a pure binding like you're used to in non-monadic code, which is what you need here.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.