Haskell 在定义前使用符号

Haskell 在定义前使用符号,haskell,Haskell,为什么在等式右侧的下面一行中,人们可以使用符号“fibs”,尽管它尚未定义: let fibs = 0 : 1 : zipWith (+) fibs (tail fibs) 它实际上不会在您的定义中调用fibs,直到稍后在您的程序中有其他东西使用fibs,此时fibs已经完全定义 您也可以在大多数其他语言中执行此操作: int foo(int x) { if (x <= 0) return 0; // will call foo when it gets there,

为什么在等式右侧的下面一行中,人们可以使用符号“fibs”,尽管它尚未定义:

let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

它实际上不会在您的定义中调用
fibs
,直到稍后在您的程序中有其他东西使用
fibs
,此时
fibs
已经完全定义

您也可以在大多数其他语言中执行此操作:

int foo(int x)
{
    if (x <= 0) return 0;

    // will call foo when it gets there, at which point its been defined
    foo(x - 1); 
}
intfoo(intx)
{

如果(x点是
fibs的定义

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
在其他地方使用之前不会计算。然后,使用已知部分展开定义。我们从
fibs=0:1:?
开始。然后,如果需要第三个元素,则会进一步计算定义

fibs = 0 : 1 : zipWith (+) (0 : 1 : ???) (tail (0 : 1 : ???))
     = 0 : 1 : zipWith (+) (0 : 1 : ???) (1 : ???)
     = 0 : 1 : (0 + 1) : zipWith (+) (1 : ???) (???)
但是未知部分
已经部分已知,它被确定为
?=1:??
,因此展开可以继续

     = 0 : 1 : 1 : zipWith (+) (1 : 1 : ????) (1 : ????)
     = 0 : 1 : 1 : 2 : zipWith (+) (1 : ????) (????)
     -- now ???? is known to be 2:?????
     = 0 : 1 : 1 : 2 : zipWith (+) (1 : 2 : ?????) (2 : ?????)
等等。

所有Haskell绑定都是递归的。这与大多数语言不同,但由于懒惰(Haskell评估不严格,与大多数流行语言相比),它通常可以正常工作。新手在尝试以下操作时经常会被绊倒:

main = do
  let a = 3
  let a = 3 + a
  print a
因为对
a
的第二个绑定实际上忽略并隐藏了第一个绑定,并根据自身定义了
a
,这会在您尝试打印
3+3+3+3+…
的结果时导致无限循环

无限列表的一个简单示例是
ones
:一个
1
s的无限列表

ones = 1 : ones
在这种情况下,“一”只是指它自己

   _______
   |     |
   v     |
________ |
| ones | |
| 1 : ---|
--------
在Haskell中,创建无限树的方式与创建无限列表的方式大致相同:

data Tree a = Stub | Branch a (Tree a) (Tree a)
onesTree = Branch 1 onesTree onesTree


______  _______
|    |  |      |
|    v  v      |
| ____________ |
| | onesTree | |
|--- | 1 | ----|
  ------------

我认为真正的问题是:为什么其他语言不能像Haskell那样方便地支持递归值呢?

好吧,要理解这一点,最好理解延迟求值是如何实现的。基本上,未求值的表达式由thunks表示:一种数据结构,它代表了实现所需的所有信息在实际需要时计算值。当后一种情况发生时(或者如我们所说,当thunk被强制时),计算值的代码被执行,thunk的内容被替换为可能有指向其他thunk的指针的结果


因此,
fibs
一开始是一个thunk。这个thunk包含指向用于计算其值的代码的指针,以及指向这个代码作为参数的thunk的指针。后一个指针之一是指向
fibs
本身的指针。

我理解命令式语言中的递归,当它被拆分为几行。例如fib0=0;fib1=1;fibn=fib(n-1)+fib(n-2).我不明白它在我在主要问题中给出的示例中的用法。@Trismegistos:完全一样,Haskell在被调用之前不需要执行此函数。当您在其他地方调用
fibs
,并且在该调用中达到
fibs
,它只需重新统计该过程。Haskell不需要调用它或者在它被定义/解析的时候做任何事情。@NiklasB。我知道什么是递归,但我不理解这个特定的一行。请看我在GManNickG答案下的评论。@NiklasB。大多数语言都会让你在自己的定义中使用一个值。很少有语言内置惰性。@Daniel:是的,没错,大多数语言只支持rt递归是针对函数的,而不是针对列表的。谢谢,乍一看,它看起来比函数递归更复杂。递归计算列表/值的语法是否有一些约束?换句话说,但不确切地说,什么是允许的语法?没有语法约束,您只需使用它所在的表达式中定义的值定义为,
value=使用value
的某个表达式。需要注意的是计算是否终止。例如,您可以尝试在result
中定义
cycle xs=let result=result++xs。但如果您尝试对其求值,您首先必须确定
result
是否为非空,以知道要使用哪个等式用于
(++)
。因此,您可以查看
result
result=result++xs
的定义,因此要确定
result
是否为非空,您必须确定
result
是否为非空……但是如果您定义了
cycle xs=let result=xs++result
,您已经对
result
了解得够多了在计算
结果时出现检查
结果的需要时卡住(除非
xs
为空,否则将处于
让x=x在x
的情况)因此,这样的自参考定义要求在定义表达式中检查
值之前,可以确定
值的某些部分,并且始终可以使用已知的内容确定下一个需要的部分。