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

Haskell:简单计算中的签名/类型混乱,haskell,Haskell,我对哈斯凯尔很陌生,我正在努力学习野兽。 下面是一个简单的函数,它将时间(以秒为单位)转换为[天、小时、分钟、秒]。 我花了半天时间在签名和类型上苦苦挣扎,但仍然出现了一个类型错误。 看起来我已经尝试了所有的签名组合-没有运气。你能帮忙吗 我需要什么签名?我首先尝试了secToTime::Integer->[Integer],得到了一大堆类型错误,这些错误非常隐晦,足以把我搞糊涂了 显示结果列表的正确方法是:main=do print secToTime还是main=do map(\x->put

我对哈斯凯尔很陌生,我正在努力学习野兽。 下面是一个简单的函数,它将时间(以秒为单位)转换为[天、小时、分钟、秒]。 我花了半天时间在签名和类型上苦苦挣扎,但仍然出现了一个类型错误。 看起来我已经尝试了所有的签名组合-没有运气。你能帮忙吗

  • 我需要什么签名?我首先尝试了
    secToTime::Integer->[Integer]
    ,得到了一大堆类型错误,这些错误非常隐晦,足以把我搞糊涂了
  • 显示结果列表的正确方法是:
    main=do print secToTime
    还是
    main=do map(\x->putstrn.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)
    

  • 谢谢

    问题是您正在混合使用
    rem
    ,它与
    整数
    s一起工作,与
    分数
    s一起工作。简单的解决方法是使用
    div
    代替
    /
    。这也意味着不需要
    truncate
    ,因为
    div
    表示整数除法

    基本上,Haskell将类似整数的类型与其他类型区分开来。
    Integral
    typeclass中的任何类型都像一个整数——包括
    Int
    integer
    和大量更专门的类型,如
    Word
    。对于您的特定代码,您希望将所有数字视为整数,因此应该使用类型为
    Integral a=>a->a->a
    的函数,例如
    div
    ,而不是
    /

    另一个注意事项:由于时间表示法始终有四个字段,因此不应使用列表。您可以使用元组
    (d、h、m、s)
    或创建自己的类型:

    data Time = Time Integer Integer Integer Integer deriving (Show, Eq)
    
    派生(Show,Eq)
    位只为您免费提供
    Show
    =
    时间
    类型

    所以,把所有这些放在一起,我们得到了这个函数:

    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
    
    请注意,这使得返回的数据所代表的内容非常明显

    由于
    时间
    派生
    显示
    ,我们只需在其上使用
    打印
    。因此,我们可以:

    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
    
    好多了。基本上,我们所做的就是通过指定
    Show
    实例并定义
    Show
    函数,告诉Haskell
    Time
    s应该如何显示为
    String
    s

    假设您有一个
    Time
    类型,您可以使用模式匹配从中提取值:

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

    模式
    (Time d h m s)
    获取您的
    Time
    值,并将其解构为包含的四个整数,分别命名为
    d
    h
    m
    s
    。这使您可以编写任意函数,通过输出实际数字,这些函数可以在
    时间
    上运行。

    就打印而言

    main = print $ secToTime 1234
    
    应该有效(因为只有一个语句,所以不需要do)。请注意,您要么需要一个
    $
    放在我放置它的位置,要么需要将
    secToTime
    及其参数用括号括起来
    print secToTime
    本身就是一个类型错误,因为无法打印函数,
    print secToTime 1234
    是双重类型错误-首先,Haskell将
    print
    应用于
    secToTime
    ,产生如上所述的类型错误,然后(假装可以打印
    secToTime
    ),Haskell将应用调用
    print
    得到的
    IO()
    ,并将其应用于
    1234
    ,这完全是胡说八道,因为
    IO()
    不是函数


    括号使
    secToTime
    首先应用于
    1234
    ,然后结果列表将传递给
    print
    $
    基本上做了相同的事情-它是一个普通函数,接受其右参数并将其传递给左参数。由于运算符绑定的函数低于正常函数,因此首先应用了
    secToTime,使所有函数都能正常工作。

    虽然我完全同意Tikhon的解决方案最适合这种特殊情况,但让我向您展示如何以一般方式从秒生成列表:

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

    非常感谢你!啊,那些小东西。。。我喜欢这种新型的建议——有利于学习。但我似乎无法从main打印它。它抱怨“没有show的实例(show(Integer->Time)),我只做了一个简单的操作:“main=print secToTime”“再次感谢。很抱歉,在看到您关于如何显示的答案之前,我发布了后续问题。创建Show Time的实例很好,但稍后我可能希望以不同的方式格式化输出。如果我想把格式留到以后呢?如何从时间类型中提取整数以进行进一步格式化/处理?作为参考,
    print secToTime
    尝试打印函数
    secToTime
    。要实际打印时间值,需要提供
    secToTime
    参数。我将通过编辑答案来解决您后面的问题。我建议使用一个带有命名字段的类型,
    data Time=Time{days,hours,minutes,seconds::Integer}
    。看起来非常。。。不同:)我正处于这样一个学习阶段,无法完全理解/欣赏上述内容。虽然我对学习很感兴趣