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

I'm really confused about function declarations in Haskell

提问人:Nico Arevalo 提问时间:4/30/2020 最后编辑:Will NessNico Arevalo 更新时间:4/30/2020 访问量:603

问:

这是一份家庭作业,所以我更喜欢只有提示或我可以学习的链接,而不是完整的答案。这是我得到的:

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

我从中了解到的是,我应该比较 3 个值(在本例中为 , , ?) 并返回它们是否都彼此相等。这是我尝试过的:aaa

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 感到完全迷茫,所以任何关于如何阅读函数或声明函数的提示都会有很大帮助。

函数 Haskell 语法 相等 定义

评论

0赞 Alejandro Alcalde 4/30/2020
声明究竟是什么让您感到困惑?
8赞 Redu 4/30/2020
类型签名正确。然后。。如果你读过 LYAH 的前几章,比如 Baby 的第一个功能,也许这是一个好主意......
1赞 mkrieger1 4/30/2020
关于你试图实现的逻辑(我不知道有多少Haskell能够判断它是否真的编译),请考虑:如果值是,,你的函数当前会返回什么?x=1y=2z=3
3赞 molbdnilo 4/30/2020
你不需要做记号或为此。在包括 Haskell 在内的大多数编程语言中,“所有三个相等等同于等于等于”的翻译非常简短。<-xyyz
5赞 molbdnilo 4/30/2020
在不相关的音符上,等价于 ,并且等价于 。if fact then True else Falsefactif fact then False else Truenot fact

答:

3赞 Micha Wiedenmann 4/30/2020 #1

让我们从一个函数开始,该函数采用单个:Int

allEqual1Int :: Int -> Bool
allEqual1Int x = True

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

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

allEqual x y z do

我们注意到您缺少 A,并且您不需要 .=do

的版本可能看起来像String

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

我们观察到,相同的实现适用于多种类型(和),只要它们支持 。IntString==

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

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

适用于任何支持 .==

1赞 Markus Appel 4/30/2020 #2

操作方法如下:

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

这是什么意思?

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

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

任何类型都有一个函数调用。它需要一个 * 的实例,并接受三个参数,所有参数的类型都是 ,并返回一个allEqualaEq aaBool

第二行说:

函数 ,对于任何参数 , , 和 ,都应该计算 ,它只是比较等于和等于 。allEqualxyzx == y && y == zxyyz

* 实例或类型类是一种语言特性,没有多少其他编程语言所具有,所以如果你对它们的含义感到困惑,我建议你先了解它们。

评论

4赞 Micha Wiedenmann 4/30/2020
OP要求“我只希望有提示或我可以学习的链接,而不是完整的答案”。我认为你不应该提供完整的答案。
0赞 Markus Appel 4/30/2020
@MichaWiedenmann 尽管你是对的,但这意味着要征求建议,这在 StackOverflow 上是不鼓励的。问题应该有明确、明确的答案。
2赞 Micha Wiedenmann 5/1/2020
请注意,a)我没有投反对票b)这完全是你的电话,我只是给出了我的意见。谢谢你的贡献。
3赞 Janusz Czornik 4/30/2020 #3

对于那些以前用不同语言编程的人来说,Haskell是一门有点奇怪的语言。我们先来看看这个函数:

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

你可以这样看:“”后面的最后一个“东西”是返回类型。预览“事物”是参数。由此,我们知道该函数接受三个参数,这些参数是并返回 .->IntBool

现在看看你的函数。

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

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

现在让我们看一下您的实现。您使用的是 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

就像在您的示例中一样,我们有三个参数,我们返回 .在第一行中,我们对所有参数调用函数。如果所有这些调用都返回 true,也将返回 true。否则,该函数将返回 false。请注意,该函数在关键字“where”之后定义。这在Haskell中很常见。BoolisEq3allEq3isEq3

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

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

6赞 bradrn 4/30/2020 #4

这个问题已经有其他几个非常好的答案来解释如何解决你的问题。我不想那样做;相反,我将遍历您的代码的每一行,逐步纠正问题,并希望能帮助您更好地理解 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

下一个错误是使用表示法。 符号因混淆初学者而臭名昭著,所以不要为滥用它而感到难过。在 Haskell 中,符号只在需要逐行执行一系列语句的特定情况下使用,尤其是当你有一些副作用(比如,打印到控制台)时,每行都会执行。显然,这不适合这里——你所做的只是比较一些值并返回一个结果,这几乎不需要逐行执行。所以让我们摆脱它:dodododo

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

接下来,另一个问题:Haskell 无效!您可能熟悉其他语言的这种语法,但在 Haskell 中,类型总是使用 指定,并且通常使用类型签名。因此,我将删除名称之前的内容,并添加类型签名:Bool check::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

现在,并且只在一个地方使用 - 给它们命名不会做任何事情,只会降低代码的可读性。因此,我将内联其用法的定义和用法:checknextCchecknextC

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<condition>

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

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

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

评论

0赞 Will Ness 4/30/2020
对于新手来说,做记法并不难;它可以(应该?!)公理化地引入,即使是色彩鲜艳的图片!:)特别是当我们考虑列表推导的不言自明程度,以及单子推导的相似程度时,它们几乎等同于 .do
0赞 bradrn 4/30/2020
@WillNess 好一点,这并不难。(我自己很容易学会。我想我应该说,它因“混淆新人”而不是“难以理解”而臭名昭著——我现在将对其进行编辑。
0赞 Will Ness 4/30/2020
是的,令人困惑,令人困惑的演示文稿总是在谈论单子和语法脱糖......
0赞 bradrn 4/30/2020
@WillNess 我认为这些演示确实是正确学习符号的唯一方法——小心避免单子教程谬误!但是,如果你对我的措辞不满意,你建议说什么?(我只是想承认一个事实,即 Haskell 的新手似乎经常误解何时使用 -notation。dodo
0赞 bradrn 4/30/2020
很高兴你喜欢它。感谢您的更正@WillNess!
2赞 ƛƛƛ 4/30/2020 #5
allEqual :: Eq a => a -> a -> a -> Bool

签名说:消耗 3 个类型的值;它生成 类型的结果。该部分限制了可能的操作;它说无论类型是什么,它都需要满足 中定义的要求。您可以在此处找到这些要求: http://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#t:Eq 您现在知道了操作可以执行的操作,然后可以通过遵循类型签名来完成函数。allEqualaBoolEq a =>aaEqa

评论

1赞 Will Ness 5/1/2020
“限制”是错误的词语选择。恰恰相反:约束规定了必须具有的操作。a