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
    \$\begingroup\$ main = do [n] <- getArgs; print $ sum [1..read n] \$\endgroup\$ – Gurkenglas Nov 24 '17 at 21:29
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\$
  • \$\begingroup\$ So (<$>) = fmap? \$\endgroup\$ – A Sz Nov 24 '17 at 20:17
  • \$\begingroup\$ @ASz Right. I'm sure I had that in a draft., sorry. \$\endgroup\$ – Zeta Nov 25 '17 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, privacy policy and cookie policy

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