让5等于10是什么意思?这不是一个赋值操作吗?

40
如果我说let 5 = 10,为什么5 + 1返回的是6而不是11

22
一个直接的答案可能是“因为你不能重新定义5是什么”。但是,我实际上对于写下let 5 = 10竟然是可能的感到相当惊讶! - duplode
3
你可以通过对 + 进行“重载”来实现:let 1+1=3 in 1+1 ;)。 - Random Dev
10
顺便说一下,我认为 let 5 = 10 中的 5 仍然是一个模式(只是永远不会匹配),因此它不会绑定任何内容(就像 let (x,5) = (6,6) 中的 5 一样)。 - Random Dev
5
@Carsten Indeed -- 这正是报告第四章所说的,如果我正确解析的话。 - duplode
4
Prelude> :set -XBangPatterns 表示开启了 BangPatterns 扩展。然后 Prelude> let !5 = 10 会产生异常 *** Exception: <interactive>:9:5-11: Non-exhaustive patterns in pattern binding,这是因为在模式匹配时没有覆盖到所有情况,而惰性计算隐藏了这个失败的匹配,导致你的误解得以继续存在。 - Thomas M. DuBuisson
显示剩余3条评论
3个回答

53

当你说

let 5 = 10

这不是对5的重新定义,而是模式匹配,就像你说的那样。

foo 5 = undefined
 ... foo 10 ...

如果模式匹配失败,则该模式将无法匹配。

在let表达式中,匹配是惰性的。这意味着只有当由其绑定的变量被评估时才会进行匹配。这使我们能够编写以下内容:

 let foo = undefined in 10
在你的表达式中,没有变量被绑定,所以此模式永远不会匹配。或许在let绑定中,没有任何变量的这种模式是毫无意义的,应该由编译器检测并报错,但语言本身并未禁止这种用法。

6
值得安慰的是,let (5,x) = (10,True) in x 会提示错误信息 *** Exception: <interactive>:2:5-21: Irrefutable pattern failed for pattern (5, x) - pigworker
3
这对于let绑定来说有些愚蠢,但并非所有情况都是如此:例如only5 xs = [5 | 5 <- xs]是有意义的(如果你接受列表推导式可能失败的话)。 - effectfully
@user3237465 是的,我只是指让绑定。正在更新。 - n. m.
2
pigworker: 而且 let (~5,x) = (10,True) in x 之所以返回 True,仅仅是因为我们明确告诉编译器假设 5 模式匹配成功。 - rampion
类似情况:let [] = [1,2] in True - rampion

17

基本上,

let 5 = 10 in ...

等同于

case 10 of ~5 -> ...

请注意~符号,它标记了一个惰性模式不可反驳模式。这是一个匹配任何内容的模式,并且将匹配推迟到某个变量被实际要求的时候。在模式5中没有变量,因此什么也不会发生。

这种情况几乎没有用处,并且可以认为编译器应该在此处发出警告。

为了说明惰性模式的含义,请考虑以下内容:

case f 3 of
  (x,y) -> g 10 x y

f 3先被评估(到WHNF),暴露出对偶构造函数。然后,x,y绑定到(尚未评估的)对组分量。最后,计算g 10,将结果应用于x(现在可能需要),然后应用于y(可能导致要求xy)。

相比之下,

case f 3 of
  ~(x,y) -> g 10 x y

不是从评估 f 3 开始。相反,x 绑定到未评估的 fst (f 3)y 绑定到未评估的 snd (f 3)。我们改为从评估 g 10 开始。然后,将其应用于 x:这可能会导致需要 x,从而触发对 f 3 的评估。然后,将结果应用于 y,引起类似的评估。大多数实现实际上会在 xy 之间共享 f 3 的结果,以便最多计算一次。


1
类似的模式匹配可以很有用。特别是,写catch m $ \ ~MyException -> ...并不罕见,其中非绑定惰性模式匹配是提供异常类型的便捷方式。但在这种情况下使用数字字面量似乎有些牵强。 - dfeuer

2

正如 @n.m. 所说,你正在进行模式匹配。以下是一些示例。

模式匹配可以成功

Prelude> let (a, 10) = (15, 10) in a
15

或失败。
Prelude> let (a, 10) = (15, 15) in a
*** Exception: <interactive>:5:5-22: Irrefutable pattern failed for pattern (a, 10)

由于 Haskell 是惰性的,如果您不使用结果值,则代码将成功。这基本上就是您正在做的事情:

Prelude> let (a, 10) = (15, 15) in "Something else"
"Something else"

请注意,类型仍需要进行检查:
Prelude> let (a, 10, 999) = (15, 15) in "Something else"

<interactive>:7:20: error:
    • Couldn't match expected type ‘(t, Integer, Integer)’
                  with actual type ‘(Integer, Integer)’In the expression: (15, 15)
      In a pattern binding: (a, 10, 999) = (15, 15)
      In the expression: let (a, 10, 999) = (15, 15) in "Something else"Relevant bindings include a :: t (bound at <interactive>:7:6)

1
由于Haskell是惰性的,如果您不使用结果值,则代码将成功。值得一提的是,默认情况下,let绑定也会惰性匹配。case (15, 15) of { (a, 10) -> "Something else" }会崩溃,而case (15, 15) of { ~(a, 10) -> "Something else" }(由于惰性匹配,等同于您的第三个示例)会成功。 - duplode
@duplode,我认为在letwhere中使用惰性默认值是一个不好的主意,这也是感叹号模式有时会令人困惑的原因。 - dfeuer
@dfeuer 那绑定到无限列表怎么办?例如 let nats = [1..] in takeWhile isOkay nats - Teodor
@Teodor,我指的是letwhere中LHS上的模式,而不是简单的变量绑定。我认为let p = e1 in e2应该与case e1 of p -> e2完全相同,无论p长什么样子。不幸的是,在Haskell中无法修复这个问题,但希望未来的语言会考虑按照我的方式处理它。 - dfeuer
@dfeuer 谢谢您的澄清。我同意您的观点。 - Teodor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接