Haskell 在定义前使用符号
为什么在等式右侧的下面一行中,人们可以使用符号“fibs”,尽管它尚未定义: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,
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
的情况)因此,这样的自参考定义要求在定义表达式中检查值之前,可以确定值的某些部分,并且始终可以使用已知的内容确定下一个需要的部分。