拥抱,功能及其计算方式

has*_*oob 2 haskell

我有一个输入:

 [ 8 `div` 2 + 1 .. ] !! 2 : [ 1 .. 3 ]
Run Code Online (Sandbox Code Playgroud)

输出是:

 [7,1,2,3]
Run Code Online (Sandbox Code Playgroud)

但是...... Haskell首先计算了什么?

我不知道优先级,7来自哪里?

kqr*_*kqr 9

你的问题包含两个问题,所以我会尽力简要回答这两个问题.

1. Haskell的运营顺序

给定一个表达式,例如2 + 4 * 3,Haskell 2+4首先计算然后将它乘以3,还是先计算4*3它然后再加两个?你可能已经猜到了,4*3先行.

你怎么知道哪个先来?您查看相关运算符的文档和/或源代码.或者你可以通过实验来解决它.在你的例子中

[ 8 `div` 2 + 1 .. ] !! 2 : [ 1 .. 3 ]
Run Code Online (Sandbox Code Playgroud)

我从经验中知道,如果你把括号放在任何地方,它们看起来像这样:

([ ((8 `div` 2) + 1) .. ] !! 2) : [ 1 .. 3 ]
Run Code Online (Sandbox Code Playgroud)

找出优先权

为了正确地解决这个问题,你需要打开ghci并输入

:info (+)
Run Code Online (Sandbox Code Playgroud)

例如,它会说些什么

infixl 6 (+)
Run Code Online (Sandbox Code Playgroud)

如果我们也为其他运营商这样做,我们可以为我们建立一个整洁的表.

infixl 7 `div`
infixl 6 +
infixr 5 :
infixl 9 !!
Run Code Online (Sandbox Code Playgroud)

ghci没有说什么(!!),但是我在Prelude中找到了列表操作源代码,并找到了说明我在表中显示的内容的行.然后你可以假设列表的工作方式有点像圆括号,所以[]方括号内的东西就在它们之外的东西之前.

优先级如何工作

现在,infix声明中运算符名称前面的数字表示运算符具有多高优先级 - 数字越大,它在其他事物之前就越多.在这种情况下,例如,我们有

infixl 7 `div`
infixl 6 +
Run Code Online (Sandbox Code Playgroud)

这意味着`div`之前+,实际上,在表达式中

8 `div` 2 + 1
Run Code Online (Sandbox Code Playgroud)

我们发现Haskell计算结果为

(8 `div` 2) + 1
Run Code Online (Sandbox Code Playgroud)

因为`div`有更高的优先权.如果对表达式的其余部分执行此操作,则最终将使用与此答案开头相同的括号.

你通常不必非常关心,因为你写的Haskell越多,你就会越了解它是如何运作的.而且大多数情况下,如果你弄错了,你也会得到一个类型错误,提醒你错了.如果有疑问,可以尝试使用括号而不使用括号ghci,看看哪一个给出了正确的答案.

2. Haskell中的计算顺序

到目前为止,我已回答了"Haskell如何解释表达式?"的问题.实际计算它的顺序是一个完全不同的问题.大多数编程语言首先计算内括号 - Haskell完全相反!

鉴于表达

([ ((8 `div` 2) + 1) .. ] !! 2) : [ 1 .. 3 ]
Run Code Online (Sandbox Code Playgroud)

Haskell一开始就会把它视为

<something>
Run Code Online (Sandbox Code Playgroud)

然后,当你要求价值时,它会呻吟一下并意识到它的说法

<something> : <something>
Run Code Online (Sandbox Code Playgroud)

它会意识到它需要进一步计算以给你一个值,所以它会扩展到

(<something> !! <something>) : [ 1 .. 3 ]
Run Code Online (Sandbox Code Playgroud)

(顺便说一句,这些<something>通常被Haskell人称为thunks.)然后它必须更深入,将其变为

([ <something> .. ] !! 2) : [ 1 .. 3 ]
Run Code Online (Sandbox Code Playgroud)

然后它需要此列表的第二个元素,因此它将扩展尽可能多的列表.

([ <something>
 , (<something> + 1)
 , (<something> + 2) .. ] !! 2) : [ 1 .. 3 ]
Run Code Online (Sandbox Code Playgroud)

然后它可以减少!!,返回列表的第三个元素(具有索引2),因此整个列表消失并被第三个元素替换.

(<something> + 2) : [ 1 .. 3 ]
Run Code Online (Sandbox Code Playgroud)

然后它可以减少:,所以结果是一个列表.

[ <something + 2>, 1, 2, 3 ]
Run Code Online (Sandbox Code Playgroud)

在这一点上,它最终必须弄清楚它<something>是什么,所以它回到它的定义并将其扩展为

[ (<something> + 1) + 2, 1, 2, 3 ]
Run Code Online (Sandbox Code Playgroud)

然后

[ ((8 `div` 2) + 1) + 2, 1, 2, 3 ]
Run Code Online (Sandbox Code Playgroud)

并且然后它开始计算的第一个元素的实际值,给你

[ (4 + 1) + 2, 1, 2, 3 ]
[ 5 + 2, 1, 2, 3 ]
[ 7, 1, 2, 3 ]
Run Code Online (Sandbox Code Playgroud)

要消除这一点的是Haskell试图计算任何值,除非它绝对必须.它尽可能地尝试仅使用值的描述,而不是任何实际值.它最终会执行所有必要的计算.

如果你没有要求列表的第一个值,它将永远不会被计算出来.

这就是"懒惰评估"的含义.