user1191478
user1191478

Reputation: 21

Haskell: Signature/type mess in a simple calculation

I am very much new to Haskell and trying to learn the beast. here is a simple function that translates time in seconds to [days,hours,minutes,seconds]. I've been struggling half a day with the signature and types and I still get a type error. It looks like I've tried all kind of signature combination - no luck. Could you please help.

  1. What signature do I need? I tried the secToTime :: Integer -> [Integer] first and got bunch of type errors that are cryptic enough to confuse the hell out of me.
  2. Would it be the correct way to display the resulting list: main = do print secToTime or main = do map (\x -> putStrLn . show) secToTime?

    secToTime secs = [d,h,m,s]
      where s = secs `rem` 60
            m = truncate(secs / 60) `rem` 60
            h = truncate(secs / 3600) `rem` 24
            d = truncate(secs / 86400)
    

Thanks

Upvotes: 2

Views: 257

Answers (3)

Landei
Landei

Reputation: 54584

While I totally agree that Tikhon's solution is the best for this special case, let me show you how you could generate list from seconds in a general way:

secToTime secs = let mods = [24,60,60]
                     (d:rest) = scanr (flip div) secs mods
                 in d: zipWith mod rest mods

Upvotes: 1

Tikhon Jelvis
Tikhon Jelvis

Reputation: 68162

The issue is that you are mixing rem, which works with Integrals and / which works with Fractionals. And easy fix would be to use div in place of /. This also means truncate is not needed because div represents integer division.

Basically, Haskell differentiates types that act like integers from other types. Any type in the Integral typeclass acts like an integer--these include Int, Integer and a ton of more specialized types like Word. For your particular code, you want to treat all the numbers like integers, so you should use functions with a type like Integral a => a -> a -> a, like div rather than /.

Another note: since your time representation always has four fields, you should not use a list. You can instead use a tuple (d,h,m,s) or create your own type:

data Time = Time Integer Integer Integer Integer deriving (Show, Eq)

The deriving (Show, Eq) bit just gives you show and == for the Time type for free.

So, putting all this together, we get this function:

secToTime secs = Time d h m s
  where s = secs `rem` 60
        m = secs `div` 60 `rem` 60
        h = secs `div` 3600 `rem` 24
        d = secs `div` 86400

So now the natural question is, what sort of type signature does this have? It is actually very simple:

secToTime :: Integer -> Time

Note how this makes it very obvious what the returned data represents!

Since Time derived Show, we can just use print on it. So we could have:

main = print $ secToTime 12345

This works because, internally, print just calls show followed by outputting the result. Since Time is an instance of Show, it has a show function defined and so can be used with print.

Running this program will print:

Time 0 3 25 45

However, this is not really an ideal result! The format it got printed in is awkward. Instead, let's first get rid of the derived Show:

data Time = Time Integer Integer Integer Integer deriving (Eq)

and write our own. Fun!

instance Show Time where 
  show (Time d h m s) = show d ++ " days " ++ 
                        show h ++ " hours " ++ 
                        show m ++ " minutes " ++ 
                        show s ++ " seconds"

Now, when we run main, we get this output:

0 days 3 hours 25 minutes 45 seconds

It's much better. Basically, all we did was tell Haskell how Times should look as Strings by specifying the Show instance and defining a show function.

Given that you have a Time type, you can extract the values from it using pattern matching:

timeToSec (Time d h m s) = d * 86400 + h * 3600 + m * 60 + s

The pattern (Time d h m s) takes your Time value and deconstructs it back into the four integers it contains, naming them d, h, m and s. This lets you write arbitrary functions that can operate on Times by getting the actual numbers out.

Upvotes: 9

Retief
Retief

Reputation: 3217

As far as printing is concerned,

main = print $ secToTime 1234

should work (since there is only one statement, you don't need a do). Notice that you either need a $ where I put it or you need to surround secToTime and its argument with parentheses. print secToTime by itself is a type error because functions cannot be printed, and print secToTime 1234 is doubly a type error - first, Haskell applies print to secToTime, producing a type error as detailed above, then (pretending that it is possible to print secToTime), Haskell will apply the IO () that results from a call to print and apply it to 1234, which is complete nonsense since a IO () is not a function.

The parentheses cause secToTime to be applied to 1234 first, and then the resulting list will be passed to print. The $ basically does the same thing - it is a normal function that takes its right argument and passes it to its left argument. Since operators bind lower than normal functions, secToTime is applied first, causing everything to work well.

Upvotes: 2

Related Questions