Haskell:签名/类型混乱的简单计算

时间:2012-02-06 06:00:05

标签: haskell

我对Haskell非常陌生,并试图学习野兽。 这是一个简单的函数,可以将时间(以秒为单位)转换为[天,小时,分钟,秒]。 我已经用签名和类型挣扎了半天,但我仍然遇到类型错误。 看起来我已经尝试过各种签名组合 - 没有运气。能帮忙吗?

  1. 我需要什么签名?我首先尝试了secToTime :: Integer -> [Integer]并且遇到了一堆类型错误,这些错误足以让我迷惑不解。
  2. 这是显示结果列表的正确方法: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)
    
  3. 由于

3 个答案:

答案 0 :(得分:9)

问题在于,您混合了rem,它与Integral/配合使用,与Fractional配合使用。简单的解决方法是使用div代替/。这也意味着不需要truncate,因为div表示整数除法。

基本上,Haskell将类似整数的类型与其他类型区分开来。 Integral类型类中的任何类型都像整数一样 - 包括IntInteger和大量更专业的类型,如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,然后输出结果。由于TimeShow的一个实例,因此定义了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值并将其解构为其包含的四个整数,并将其命名为dhm和{ {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,然后将结果列表传递给1234print基本上做同样的事情 - 它是一个正常的函数,它接受正确的参数并将其传递给它的左参数。由于运算符绑定低于正常函数,因此首先应用$,导致一切正常。

答案 2 :(得分:1)

虽然我完全同意Tikhon的解决方案对于这个特殊情况是最好的,但是让我向您展示如何以一般方式从秒开始生成列表:

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