在Haskell源文件中,我可以写
a = 1
我的印象是我必须在GHCi中写相同的
let a = 1
,GHCi中的a = 1
在=
上给出了解析错误。
现在,如果我写
a = 1
a = 2
在源文件中,我会收到有关a
的多重声明的错误,但在GHCi中写入确定:
let a = 1
let a = 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中,都会失败。 a
和True
都不能有一个变量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]
我在这里定义了一个交替True
和False
的无限列表,并使用它来得出有限的结果。
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 请注意,您之前在阴影之前定义的任何内容仍然会像以前一样完全,指的是之前的名称。这包括返回变量值的函数。最简单的理解阴影是引入一个碰巧与前一个相同名称的不同的变量,而不是试图将其理解为实际更改早期变量名称所指的内容。