我对 Haskell 中的函数声明感到很困惑

I'm really confused about function declarations in Haskell

这是一项家庭作业,所以我宁愿只提供提示或 link 我可以学习的地方,而不是完整的答案。这是我得到的:

allEqual :: Eq a => a -> a -> a -> Bool

据我了解,我应该比较 3 个值(在本例中为 aaa?)和 return 是否或并非他们都彼此平等。这是我试过的:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z do
  Bool check <- x == y
  Bool nextC <- y == z
  if check == nextC
    then True
    else False

老实说,我对 Haskell 感到完全迷失了,所以任何关于如何阅读函数或声明它们的技巧都会有很大帮助。

让我们从接受单个 Int:

的函数开始
allEqual1Int :: Int -> Bool
allEqual1Int x = True

allEqual1Int' :: Int -> Bool
allEqual1Int' x = 
  if x == x
  then True
  else False

如果我们将其与您的行进行比较

allEqual x y z do

我们注意到您错过了 = 而您不需要 do

String 的版本可能类似于

allEqual1String' :: String -> Bool
allEqual1String' x = 
  if x == x
  then True
  else False

并且我们观察到 相同的实现 适用于多种类型(IntString),前提是它们支持 ==

现在a是一个类型变量,把它想象成一个变量,这样它的值就是一个类型。给定类型支持 == 的要求在 Eq a 约束中编码(将其视为接口)。因此

allEqual :: Eq a => a -> a -> a -> Bool

适用于任何支持==的类型。

操作方法如下:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z = x == y && y == z

这是什么意思?

第一行定义函数的类型签名

用人类的话来说,它会说这样的话:

There is a function called allEqual for any type a. It requires an instance of Eq a* and takes three parameters, all of type a, and returns a Bool

第二行说:

The function allEqual, for any parameters x, y, and z, should evaluate x == y && y == z, which simply compares that x equals y and y equals z.

* 实例或类型 类 是许多其他编程语言所没有的语言特性,因此如果您对它们的含义感到困惑,我建议您先了解它们。

Haskell对于以前使用不同语言编程的人来说是一种有点陌生的语言。我们先来看看这个函数:

allEqual :: Int -> Int -> Int -> Bool

你可以这样看:“->”之后的最后一个"thing"是一个return类型。预览 "things" 是参数。由此,我们知道该函数接受三个参数,即 Int 和 return 以及 Bool

现在看看你的函数。

allEqual :: Eq a => a -> a -> a -> Bool

有一个额外的语法“Eq a =>”。它基本上所做的是声明中的所有以下“a”必须实现Eq。所以它是第一个函数的更通用的版本。它接受实现“Eq”和returns Bool的三个参数。该函数可能应该做的是检查所有值是否相等。

现在让我们看看您的实现。您正在使用 do 语法。我觉得一开始这不是最好的方法。让我们实现一个非常相似的函数来检查所有参数是否都等于 3。

allEq3 :: Int -> Int -> Int -> Bool
allEq3 x y z = isEq3 x && isEq3 y && isEq3 z
  where
    isEq3 :: Int -> Bool
    isEq3 x = x == 3

就像你的例子一样,我们有三个参数,我们 return Bool。在第一行中,我们对所有参数调用函数 isEq3。如果所有这些调用 return 为真,allEq3 也将 return 为真。否则,该函数将 return false。请注意,函数 isEq3 是在关键字 "where" 之后定义的。这在Haskell.

中是很常见的事情

所以我们在这里所做的是将检查所有参数是否等于 3 的大问题分解成更小的部分,检查一个值是否等于 3。

您可以大大改进此实现,但我认为这是在 Haskell 中迈出第一步的最佳方式。如果你真的想学习这门语言,你应该看看 this

这个问题已经有其他几个很好的答案来解释如何解决你的问题。我不想那样做;相反,我将检查您的每一行代码,逐步更正问题,并希望能帮助您更好地理解 Haskell。

首先,为方便起见,我将复制您的代码:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z do
  Bool check <- x == y
  Bool nextC <- y == z
  if check == nextC
    then True
    else False

第一行是类型签名;这在其他答案中已经很好地解释了,所以我将跳过它并继续下一行。

第二行是定义函数的地方。你错过的第一件事是你需要一个等号来定义一个函数:函数定义语法是 functionName arg1 arg2 arg3 … = functionBody,你不能删除 =。所以让我们更正一下:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z = do
  Bool check <- x == y
  Bool nextC <- y == z
  if check == nextC
    then True
    else False

下一个错误是使用 do 表示法。 do 符号是出了名的容易混淆初学者,所以不要因为误用它而难过。在 Haskell 中,do 符号仅在需要逐行执行一系列语句的特定情况下使用,尤其是当您有一些副作用时(例如,打印到控制台),每行执行一次。显然,这不适合这里——您所做的只是比较一些值并 return 计算结果,这几乎不需要逐行执行。所以让我们去掉那个 do:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  let Bool check = x == y
      Bool nextC = y == z
  in
    if check == nextC
      then True
      else False

(我还用 let … in … 替换了 <- 绑定,因为 <- 只能在 do 块中使用。)

接下来还有一个问题:Bool check无效Haskell!您可能熟悉其他语言的这种语法,但在 Haskell 中,类型总是使用 :: 指定,并且通常带有类型签名。所以我将删除名称前的 Bool 并改为添加类型签名:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  let check :: Bool
      check = x == y
      nextC :: Bool
      nextC = y == z
  in
    if check == nextC
      then True
      else False

现在,此时,您的程序完全有效 Haskell — 您将能够编译它,它会工作。但是您仍然可以进行一些改进。

首先,您不需要包含类型 — Haskell 具有类型推断,在大多数情况下,省略类型是可以的(尽管传统上将它们包含在函数中)。所以让我们去掉 let:

中的类型
allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  let check = x == y
      nextC = y == z
  in
    if check == nextC
      then True
      else False

现在,checknextC 只用在一个地方——给它们命名没有任何作用,只会降低代码的可读性。所以我将 checknextC 的定义内联到它们的用法中:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  if (x == y) == (y == z)
    then True
    else False

最后,我看到你有一个 if <condition> then True else False 形式的表达式。这是多余的——你可以简单地 return 和 <condition> 具有相同的含义。所以让我们这样做:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z = (x == y) == (y == z)

这比您开始使用的代码好得多!

(实际上你还可以对这段代码做一个改进。在这一点上,你的代码有一个错误应该很明显。你能找到它吗?如果是,你能修复它吗?提示:您可以使用 && 运算符将两个布尔值“和”在一起。)

allEqual :: Eq a => a -> a -> a -> Bool

签名说:allEqual消耗3个a类型的值;它产生 Bool 类型的结果。 Eq a => 部分限制了 a 可以拥有的可能操作;它说 a 是什么类型,它需要满足 Eq 中定义的要求。您可以在此处找到这些要求:http://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#t:Eq 您现在知道 a 可以做什么操作,然后您可以按照类型签名完成您的功能。