我对Haskell非常陌生,并试图学习野兽。 这是一个简单的函数,可以将时间(以秒为单位)转换为[天,小时,分钟,秒]。 我已经用签名和类型挣扎了半天,但我仍然遇到类型错误。 看起来我已经尝试过各种签名组合 - 没有运气。能帮忙吗?
secToTime :: Integer -> [Integer]
并且遇到了一堆类型错误,这些错误足以让我迷惑不解。这是显示结果列表的正确方法:main = do print secToTime
还是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)
由于
答案 0 :(得分:9)
问题在于,您混合了rem
,它与Integral
和/
配合使用,与Fractional
配合使用。简单的解决方法是使用div
代替/
。这也意味着不需要truncate
,因为div
表示整数除法。
基本上,Haskell将类似整数的类型与其他类型区分开来。 Integral
类型类中的任何类型都像整数一样 - 包括Int
,Integer
和大量更专业的类型,如Word
。对于您的特定代码,您希望将所有数字视为整数,因此您应使用Integral a => a -> a -> a
类型的函数,例如div
而不是/
。
另一个注意事项:由于你的时间表示总是有四个字段,你应该不使用一个列表。您可以改为使用元组(d,h,m,s)
或创建自己的类型:
data Time = Time Integer Integer Integer Integer deriving (Show, Eq)
deriving (Show, Eq)
位只为show
类型免费提供==
和Time
。
所以,把所有这些放在一起,我们得到了这个功能:
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
所以现在自然的问题是,这有什么类型的签名?它实际上非常简单:
secToTime :: Integer -> Time
请注意这是如何使非常明显返回数据代表的内容!
自Time
派生Show
以来,我们可以在其上使用print
。所以我们可以:
main = print $ secToTime 12345
这是有效的,因为在内部print
只调用show
,然后输出结果。由于Time
是Show
的一个实例,因此定义了show
函数,因此可以与print
一起使用。
运行此程序将打印:
Time 0 3 25 45
但是,这不是一个理想的结果!它打印的格式很尴尬。相反,让我们首先摆脱派生的Show
:
data Time = Time Integer Integer Integer Integer deriving (Eq)
并写下我们自己的。有趣!
instance Show Time where
show (Time d h m s) = show d ++ " days " ++
show h ++ " hours " ++
show m ++ " minutes " ++
show s ++ " seconds"
现在,当我们运行main
时,我们得到了这个输出:
0 days 3 hours 25 minutes 45 seconds
好多了。基本上,我们所做的只是通过指定Time
实例并定义String
函数来告诉Haskell Show
s show
s应该是什么样的。{/ p>
鉴于您有Time
类型,您可以使用模式匹配从中提取值:
timeToSec (Time d h m s) = d * 86400 + h * 3600 + m * 60 + s
模式(Time d h m s)
获取您的Time
值并将其解构为其包含的四个整数,并将其命名为d
,h
,m
和{ {1}}。这使您可以通过获取实际数字来编写可以在s
上运行的任意函数。
答案 1 :(得分:2)
就印刷而言,
main = print $ secToTime 1234
应该有用(因为只有一个语句,你不需要做)。请注意,您需要{I}放置它,或者您需要用括号括起$
及其参数。 secToTime
本身是一个类型错误,因为函数无法打印,print secToTime
是一个类型错误 - 首先,Haskell将print secToTime 1234
应用于print
,产生类型错误如上所述,(假装可以打印secToTime
),Haskell将应用调用secToTime
产生的IO ()
并将其应用于print
,由于1234
不是函数,所以完全是胡说八道。
括号会首先将IO ()
应用于secToTime
,然后将结果列表传递给1234
。 print
基本上做同样的事情 - 它是一个正常的函数,它接受正确的参数并将其传递给它的左参数。由于运算符绑定低于正常函数,因此首先应用$
,导致一切正常。
答案 2 :(得分:1)
虽然我完全同意Tikhon的解决方案对于这个特殊情况是最好的,但是让我向您展示如何以一般方式从秒开始生成列表:
secToTime secs = let mods = [24,60,60]
(d:rest) = scanr (flip div) secs mods
in d: zipWith mod rest mods