【问题标题】:difference between variable definition in a Haskell source file and in GHCi?Haskell 源文件和 GHCi 中的变量定义之间的区别?
【发布时间】:2016-07-18 21:04:48
【问题描述】:

在 Haskell 源文件中,我可以写

a = 1

我的印象是我必须在 GHCi 中编写与

相同的内容
let a = 1

,对于 GHCi 中的 a = 1,在 = 上会出现解析错误。

现在,如果我写

a = 1
a = 2

在一个源文件中,我会得到一个关于a的多重声明的错误,但是用GHCi写是OK

let a = 1
let a = 2

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

【问题讨论】:

  • 对我来说,GHCi 模糊(!)就像你在一个巨大的 IO-typed do 块中。除了 1 + 2 这样的表达式仍然有效。您只能使用let 绑定do 块中的变量。 Haskell 模块(或“源文件”)是完全不同的野兽。
  • 我在您显示的两个 let 片段上遇到 ghc 解析错误。
  • @chepner 谢谢。我的意思是两个let 在 GHCi 中给出了一个错误。更新
  • 我总是发现这个错误奇怪地不一致;我几乎会争辩说它应该是一个重叠模式警告而不是一个错误(作为a 的两个定义子句的参数出现的所有零模式都是相同的!)。将编译文件时收到的消息与a () = 1; a () = 2 进行比较。
  • @DanielWagner 这是一个很好的观点......虽然如果你将它们解释为仅仅是重叠的模式并让它被现有的警告覆盖,你会得到的行为是 first 定义会胜出,而不是最后一个,这几乎可以肯定是令人惊讶的。 “从不有用且通常令人惊讶”似乎值得添加一个特殊情况以使其成为错误,而不是像重叠模式警告那样“从不有用但至少直观”。

标签: haskell ghci


【解决方案1】:

交互式解释器中的连续let“语句”实际上相当于嵌套的let 表达式。它们的行为就好像在赋值之后有一个隐含的in,而解释器会话的其余部分包括let 的主体。那是

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

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

【讨论】:

    【解决方案2】:

    Haskell 的一个关键区别在于有两个同名和相同作用域的定义,以及在嵌套作用域中有两个同名定义。文件中的 GHCi vs modules 与这里的底层概念并没有真正的关系,但是如果您不熟悉这些情况,这些情况确实会导致您遇到问题。

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

    let a = True
        a = False
    in  a
    

    或者用大括号和分号(不开启多行模式更方便粘贴到GHCi):

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

    这将失败,无论是在模块中还是在 GHCi 中。不能有一个变量a 既是True 又是False,并且在同一个范围内不能有两个名为a 的单独变量(或者不可能知道哪个变量被引用到源文本a)。

    单个绑定集中的变量都是“一次”定义的;它们的写入顺序根本不相关。您可以看到这一点,因为可以定义相互引用的相互递归绑定,并且不可能以任何顺序一次定义一个:

    λ 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 表达式中一样,所有定义“同时发生”1;它们仅按特定顺序排列,因为将它们写在文件中不可避免地会引入顺序。所以在一个模块中:

    a = True
    a = False
    

    如您所见,给您一个错误。

    在 do-block 中有 let-statements 而不是 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-statements 和 let-expressions,因为语句主要由表达式组成,而 let-expression 作为表达式总是有效的。

    3 您可以将其视为一般规则,即 do 块中的后面语句在概念上嵌套在所有早期语句中,因为当您将它们转换为一元操作时,它们就是这个意思;实际上,let 语句实际上被转换为 let 表达式,其余的 do 块位于 in 部分中。

    4 虽然不可能引用任何更远的定义。

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

    【讨论】:

    • 你的第一句话就说得差不多了。感谢您的澄清。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-25
    • 2011-08-18
    • 2011-08-18
    • 2011-04-27
    相关资源
    最近更新 更多