【问题标题】:Difference between haskell tutorialshaskell 教程之间的区别
【发布时间】:2014-12-05 05:33:42
【问题描述】:

我最近开始在讲座中学习 Haskell,但是当我尝试找到进一步的解释以了解它在网上的工作原理时,我发现代码看起来完全不同。注意:我之前只学过 C,并且习惯了大多数格式相似的代码,看起来也很相似。

例如,在我的讲座中,一个数字的阶乘函数如下所示:

{-**********-}

fac :: Int -> Int
fac n
    | n==0  = 1
    | n>0   = n * fac (n-1)

{-**********-}

但是当我在网上看时,它看起来完全不同,而且要简单得多。例如:

factorial 1 = 1
factorial k = k * factorial (k-1)

谁能解释为什么我所学的代码更有效,特别是它说的地方

fac:: Int->Int 

为什么这很重要?

【问题讨论】:

  • 正如答案所指出的,这些是编写基本相同功能的不同方式。可能值得指出的是facfactorial 的定义不是同一个函数,它们在应用于0 时具有不同的含义。这里还有一些解决相同问题的方法(主要是一个笑话)willamette.edu/~fruehr/haskell/evolution.html
  • @Cirdec 不错的链接。它可以很有教育意义:)
  • 第一个效率较低,因为它进行了额外的毫无意义的比较 (n>0)。
  • 第一个对于小于零的数字是未定义的,将退出并出现错误。对于小于零的数字,第二个将进入无限循环。

标签: haskell ghci


【解决方案1】:

您要询问的行是函数类型签名,很像 C 中的函数原型声明。具体行说函数 fac 采用 Int 参数并返回 Int 结果。这在 Haskell 中并不总是必要的,因为它可以从使用函数的上下文中推断出类型。其他区别只是处理案件的不同方法。第一个叫做guards,第二个叫做pattern matching
一般来说,我强烈推荐这个资源来轻松有趣地学习 Haskell:Learn You a Haskell for Great Good!

更新:
只是简要解释一下为什么在这种特定情况下类型签名是必不可少的。假设您有两种不同的功能,一种有签名,另一种没有:

factorial1 :: Int -> Int
factorial1 n
    | n==0  = 1
    | n>0   = n * factorial1 (n-1)


factorial2 1 = 1
factorial2 k = k * factorial2 (k-1)

如果我们尝试做类似factorial1 3.5 的事情,haskell 会抛出一个错误,因为它知道3.5 不是一个 Int。但是对于factorial2,haskell 只知道我们正在处理数字,3.5 就可以了。但是对于输入3.5,它将以无限循环结束,因为它永远不会达到边界条件(k=1)。这就是为什么有时签名是必不可少的。有时相反,您希望您的函数对不同类型具有通用性,但在这种情况下,您仍然应该有一些签名来定义一些更通用的类型约束(haskell 有这样的功能)。

【讨论】:

  • 好的,谢谢,这是有道理的。如果无论如何都可以推断出来,那么声明它有什么好处吗?或者更确切地说,不申报有什么负面影响?
  • @ElisJones 从具有显式 sig 的函数生成文档更容易,如果您手动缩小类型,一些类型错误更容易解决。我通常只是在模块边界上添加信号。
  • @ElisJones 这里有一个类似的问题:stackoverflow.com/questions/27067905/… 但它是在谈论一些您现阶段可能难以理解的高级内容
  • @ElisJones 刚刚添加了一些示例来解决(简短且不完整)这个问题
  • @ElisJones 类型签名有时是 Haskell 面对歧义时需要的;例如,当您开始编写简单的脚本时,您可能会发现自己在编写 readLn :: IO DoublereadLn :: IO IntreadLn :: IO Integer - 这里发生的是 Haskell 可以读取用户输入"3" 作为双精度浮点数或系统字长有符号整数或任意大的有符号整数。但是考虑类型也有助于编写函数。通过说“我期望什么参数?”你限制了你可以用它们做什么,这很好。
【解决方案2】:
fac:: Int->Int 

是函数的类型签名。它接受一个 Int 类型的参数并返回一个 Int。

这些对文档很有用。

Haskell 能够自行推断类型,请参阅此链接:https://www.haskell.org/haskellwiki/Type_inference

“类型推断是类型系统的一个特性,这意味着具体类型由类型系统推导出来”

这意味着我们不必提供类型签名。

第一个例子使用了一个叫做守卫的东西。有关守卫的更多信息,请参阅此链接: http://learnyouahaskell.com/syntax-in-functions#guards-guards

第二个使用模式匹配。 (见上面的链接,向上滚动到模式匹配)

在这种情况下,它们只是解决同一问题的两种不同方法。

【讨论】:

    【解决方案3】:

    这两种方法都没有好坏之分。它们只是偏好问题。话虽如此,这两个函数都应该具有类型签名Int -> Int(即使它们在两种情况下都是可选的),因为它提高了可读性,并且如果你做错了什么,类型检查器会抱怨。所以这两个代码实际上是相同的:您在第一个版本中使用守卫,在第二个版本中使用模式匹配。

    另外,this question 很好地说明了为什么类型签名对您的两个函数都有好处。

    【讨论】:

      【解决方案4】:

      如果您想了解更多关于这些类型签名的信息,在 GHCI 或您自己的程序中使用 :t 工具非常有用。如果您对为什么会收到类型与预期类型不匹配的错误感到好奇,您总能看到函数的类型是什么。所以在 GHCI 中只需输入 :t take 或 :t repeat :t replicate ...你就明白了。当您进入列表和元组时会有很大帮助...

      【讨论】:

        猜你喜欢
        • 2019-05-02
        • 2011-08-18
        • 2011-08-18
        • 2016-09-28
        • 1970-01-01
        • 2020-03-17
        • 1970-01-01
        • 1970-01-01
        • 2010-12-28
        相关资源
        最近更新 更多