Haskell的新手,但是我遇到了令我困惑的事情,我无法解释它或找到任何文档。
如果我有以下代码:
-- Simple 2D Coordinate Data Structure
data Point = Point
{ x :: !Float
, y :: !Float }
deriving (Show)
-- Constant origin (always at (0,0) unless otherwise specified)
origin = Point 0 0
-- Return a Point that has been shifted by the specified amounts
move :: Point -> Float -> Float -> Point
move p dx dy = Point { x=xs+dx, y=ys+dy }
where
xs = x p
ys = y p
现在,此代码可以完美运行
*Main> origin
Point {x = 0.0, y = 0.0}
*Main> move origin 1 3
Point {x = 1.0, y = 3.0}
*main> otherpoint = move origin 4 4
*main> origin
Point {x = 0.0, y = 0.0}
*main> otherpoint
Point {x = 4.0, y = 4.0}
*main> :t otherpoint
otherpoint :: Point
如果我尝试移动一个点并将其分配给自己,就会出现问题
*main> otherpoint = move otherpoint 5 5
*main> otherpoint
^CInterrupted --Freezes
*main> otherpoint = move otherpoint 6 6
^CInterrupted --Freezes
答案 0 :(得分:6)
=
不执行分配; 等于两件事。您通过说otherpoint
和move otherpoint 5 5
是同一事物并且可以互换来创建递归定义。这意味着当您尝试评估对move
的调用时,它会尝试评估otherpoint
,从而导致对move
的下一次调用,等等。
您不能简单地在Haskell中“重新绑定”变量。而是使用其他名称。
nextpoint = move otherpoint 5 5
答案 1 :(得分:3)
让我们看一个有效的自引用定义示例:
Prelude> let a = 1:a
Prelude> take 10 a
[1,1,1,1,1,1,1,1,1,1]
正如chepner所述,Haskell中的等号不是赋值,而是定义。它的语义与等号(例如C或Java)截然不同。通常,Haskell中的每个变量只能被赋予一个定义,而不能更改。因此,上面的示例之所以有效,是因为它将a
定义为头为1
而尾为a
的列表。当take 10 a
试图找到a
的第二个元素时,那就是a
尾部的头。 a
的尾巴是a
,因此a
的尾巴的头是1
。因为Haskell懒惰地仅评估它需要的a
的元素,所以它计算有限次数的递归然后停止,但是如果您尝试计算length a
,它将陷入无限循环。>
您写的等同于a = a + 1
。也就是说,帖子底部的point
的定义类型正确,但是无限递归。递归计算永远不会简化。它没有充分的依据。