自引用定义在Haskell中不起作用

时间:2018-06-27 18:50:03

标签: haskell

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

2 个答案:

答案 0 :(得分:6)

=不执行分配; 等于两件事。您通过说otherpointmove 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的定义类型正确,但是无限递归。递归计算永远不会简化。它没有充分的依据。