Haskell源文件和GHCi中的变量定义之间的区别?

时间:2016-07-18 21:04:48

标签: haskell ghci

在Haskell源文件中,我可以写

a = 1

我的印象是我必须在GHCi中写相同的

let a = 1

,GHCi中的a = 1=上给出了解析错误。

现在,如果我写

a = 1
a = 2

在源文件中,我会收到有关a的多重声明的错误,但在GHCi中写入确定

let a = 1
let a = 2

有人可以帮助澄清这两种风格之间的区别吗?

2 个答案:

答案 0 :(得分:4)

连续let"陈述"交互式解释器中的实际上相当于嵌套的let表达式。它们的行为就好像在赋值后有隐含的in,并且解释器会话的其余部分包含let的正文。那是

>>> let a = 1
>>> let a = 1
>>> print a

相同
let a = 1 in
let a = 1 in
print a

答案 1 :(得分:0)

Haskell中存在一个关键区别,即具有相同名称和相同范围的两个定义,并且在嵌套作用域中具有两个相同名称的定义。 GHCi vs文件中的模块与这里的基础概念并不相关,但如果您不熟悉它们,这些情况会导致您遇到问题。

let-expression(和do块中的let语句)创建具有相同范围的绑定的 set ,而不仅仅是单个绑定。例如,作为表达式:

let a = True
    a = False
in  a

或者使用大括号和分号(在不打开多行模式的情况下更方便地粘贴到GHCi中):

let { a = True; a = False} in a

无论是在模块中还是在GHCi中,都会失败。 aTrue都不能有一个变量False,并且在同一范围内不能有两个名为a的独立变量(或者它)不可能知道源文本a)引用了哪一个。

单个绑定集中的变量全部定义为"一次&#34 ;;他们所写的订单根本不相关。您可以看到这一点,因为它可以定义所有相互引用的互斥绑定,并且不能以任何顺序一次一个地定义:

λ let a = True : b
|     b = False : a
| in  take 10 a
[True,False,True,False,True,False,True,False,True,False]
it :: [Bool]

我在这里定义了一个交替TrueFalse的无限列表,并使用它来得出有限的结果。

Haskell模块是单个范围,包含文件中的所有定义。正如在具有多个绑定的let-expression中一样,所有定义"一次发生" 1 ;它们仅按特定顺序排列,因为将它们写入文件不可避免地会引入订单。所以在这个模块中:

a = True
a = False
正如您所见,

会给您一个错误。

在一个do-block中你有let语句而不是let-expressions。 2 这些不具有in部分,因为它们只覆盖整个其余部分do-block。 3 GHCi命令就像在IO do-block中输入语句一样,所以你在那里有相同的选项,这就是你所做的事情。在你的例子中重新使用。

但是,您的示例有两个 let-bindings,而不是一个。因此,在两个不同的范围中定义了两个名为a的独立变量。

Haskell并不关心(几乎曾经)关于不同定义的书面顺序,但它 关心"嵌套顺序"嵌套范围;规则是当你引用变量a时,你得到a的最内层定义,其范围包含引用。 4

另外,通过在内部范围中重用名称来隐藏外部范围名称被称为阴影(我们说内部定义会影响外部名称)。这是一个有用的通用编程术语,因为这个概念出现在许多语言中。

因此,关于何时可以定义名称两次的规则在GHCi与模块中的区别不同,只是因为不同的上下文使得不同的事情变得更容易。

如果你想在模块中放置一堆定义,那么最简单的方法就是将它们作为所有顶级定义,它们都具有相同的范围(整个模块),因此如果你使用它会出错两次同名。你需要更多地工作来嵌套定义。

在GHCi中,您一次一个地输入命令,使用多行命令或括号和分号样式的工作更多,所以当你想要的时候很容易输入几个定义是使用几个let语句,因此如果重用名称,最终会影响早期定义。 5 你必须更有意地尝试在同一范围内实际输入多个名称。

1 或更准确地说,绑定"只是"没有任何概念"它们发生的时间"一点都不。

2 或者更确切地说:你有let语句和let-expressions,因为语句主要由表达式组成,而let-expression总是作为表达式有效。

3 您可以将此视为一般规则,即do-block中的后续语句在概念上嵌套在所有早期语句中,因为它们将它们翻译为monadic时的含义是什么操作;事实上,let语句实际上被转换为let-expression,其余部分都在in部分内。

4 虽然不可能引用任何进一步的定义,但它并不像两个在同一范围内具有相同名称的变量那样含糊不清。

5 请注意,您之前在阴影之前定义的任何内容仍然会像以前一样完全,指的是之前的名称。这包括返回变量值的函数。最简单的理解阴影是引入一个碰巧与前一个相同名称的不同的变量,而不是试图将其理解为实际更改早期变量名称所指的内容。