Ke Vin
Ke Vin

Reputation: 2024

Returning printf from function in main IO fails to print with putStrLn

I want to write a small script which calculates the differences between two timestamps and returns the hours formatted like hh:mm. For example:

./calc-hours 8 32 15 42 should return 7:10h

Note: I don't really care about edge cases which go from 23:00 to 01:00. The code below may even be wrong still but I have a different problem.

The code I came up with is this:

import Text.Printf
import System.Environment   
import Data.List  

-- Time in minutes starting from 00:00h
tm h m = h * 60 + m
-- Time difference between two timestamps in minutes
td h1 m1 h2 m2 = (tm h2 m2) - (tm h1 m1)
-- Print time difference formatted in hh:mm format
ptdf (h1:m1:h2:m2:hms) 
  | hms /= [] = printf "usage: calc-hours <h1> <m1> <h2> <m2> %s" (show "test")
  | otherwise = printf "%s:%s until %s:%s => %d:%dh" (show h1) (show m1) (show h2) (show m2) h3 m3
  where 
    d = td h1 m1 h2 m2;
    h3 = div d 60;
    m3 = mod d 60

main = do  
    args <- getArgs
    putStrLn . ptdf args

I tested these tm, td and ptdf functions directly with ghci and they worked so far. When I compile this little script with ghc I get the following error:

☁ ghc calc-hours.hs && ./calc-hours 8 32 15 42
[1 of 1] Compiling Main             ( calc-hours.hs, calc-hours.o )

calc-hours.hs:18:5: error:
    • Couldn't match expected type ‘IO b’
                  with actual type ‘a0 -> IO ()’
    • Probable cause: ‘(.)’ is applied to too few arguments
      In a stmt of a 'do' block: putStrLn . ptdf args
      In the expression:
        do args <- getArgs
           putStrLn . ptdf args
      In an equation for ‘main’:
          main
            = do args <- getArgs
                 putStrLn . ptdf args
    • Relevant bindings include
        main :: IO b (bound at calc-hours.hs:16:1)
   |
18 |     putStrLn . ptdf args
   |     ^^^^^^^^^^^^^^^^^^^^

Could someone please explain what is going wrong here and how I can fix it? I was under the impression that ptdf returns a String or at least something with which putStrLn can work with. :t printf also does not really point me in any direction.

Prelude> :t printf
printf :: PrintfType r => String -> r

Upvotes: 1

Views: 103

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476557

The problem is not the printf, the problem is that your ptdf args returns an PrintfType r => r. Here in this case, the idea is that this will use String. You are writing putStrLn . ptdf args, so you are constructing a function, but putStrLn . ptdf args should be an IO a since getArgs is an IO [String].

main :: IO ()
main = do
    args <- getArgs
    ptdf args

For your ptdf function, you will need to parse the parameters with read:

ptdf :: [String] -> IO ()
ptdf [h1, m1, h2, m2] = printf "%s:%s until %s:%s => %d:%dh\n" h1 m1 h2 m2 h3 m3
  where d = td (read h1) (read m1) (read h2) (read m2)
        h3 = div d 60
        m3 = mod d 60
ptdf _ = printf "usage: calc-hours <h1> <m1> <h2> <m2> %s\n" "test"

or we can let ptdf generate a String:

ptdf :: [String] -> String
ptdf [h1, m1, h2, m2] = printf "%s:%s until %s:%s => %d:%dh" h1 m1 h2 m2 h3 m3
  where d = td (read h1) (read m1) (read h2) (read m2)
        h3 = div d 60
        m3 = mod d 60
ptdf _ = printf "usage: calc-hours <h1> <m1> <h2> <m2> %s" "test"

and then print that string with:

main :: IO ()
main = do
    args <- getArgs
    putStrLn (ptdf args)

Upvotes: 1

Related Questions