Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell: Signature/type mess in a simple calculation

Tags:

haskell

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

like image 553
user1191478 Avatar asked Nov 23 '25 23:11

user1191478


2 Answers

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.

like image 100
Tikhon Jelvis Avatar answered Nov 25 '25 15:11

Tikhon Jelvis


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.

like image 25
Retief Avatar answered Nov 25 '25 14:11

Retief