Reputation: 21
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.
secToTime :: Integer -> [Integer]
first and got bunch of type errors that are cryptic enough to confuse the hell out of me.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
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
Reputation: 68162
The issue is that you are mixing rem
, which works with Integral
s and /
which works with Fractional
s. 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 Time
s should look as String
s 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 Time
s by getting the actual numbers out.
Upvotes: 9
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