Haskell: let 语句,将数据类型复制到自身 with/without 修改不起作用

Haskell: let statement, copy data type to itself with/without modification not working

我想通过更改一个字段来更新记录语法,所以我做了类似的事情:

let rec = rec{field = 1}

但我注意到我无法再打印 rec,这意味着当我尝试时编译器似乎进入了无限循环。所以我尝试这样做:

let a = 1 -- prints OK

let a = a -- now i can't print a (also stuck in a loop) 

所以我不能let a = a任何类型,但我不明白为什么,我应该如何解决这个问题。

顺便说一句:在做的时候:

let b = a {...record changes..}

let a = b

有效,但似乎多余。

您 运行 遇到的问题是 all letwhere Haskell 中的绑定是递归的默认。所以当你写

let rec = rec { ... }

它试图定义一个循环数据类型,当您尝试对其求值时,它将永远循环(就像 let a = a)。

没有真正的解决办法——这是语言的权衡。它使递归函数(甚至是纯值)更容易编写,噪音更小,但也意味着您不能轻易地根据自身多次重新定义 a

您真正能做的唯一一件事就是给您的值不同的名称——recrec' 是常见的做法。

公平地说 Haskell,递归函数甚至递归值经常出现。代码如

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

一旦你掌握了它就会非常好,而且不必明确地将这个定义标记为递归(就像你必须在 OCaml 中做的那样)是一个明确的好处。

首先,Haskell不允许"copying"数据给自己,这在正常意义上意味着数据是可变的。在 Haskell 中,您没有可变的 "variable",因此您将无法修改给定变量呈现的值。

您所做的只是定义一个新变量,该变量与之前的版本同名。但是,要正确执行此操作,您必须引用 old 变量,而不是新定义的变量。所以你原来的定义

let rec = rec { field=1 }

是一个递归定义,名字rec引用它自己。但是您打算做的是参考早期定义的 rec

所以这是一个名称冲突。

Haskell 有一些机制可以解决这个问题。一个是你的 "temporary renaming"。

原始示例看起来像这样

let rec' = rec
let rec = rec' { field=1 }

这看起来像您给定的解决方案。但请记住,这仅适用于命令行环境。如果你试图在一个函数中使用它,你可能必须写

let rec' = rec in let rec = rec' { field=1 } in ...

这是另一个解决方法,当 rec 属于另一个模块时(比如“MyModule”),它可能会有用:

let rec = MyModule.rec { field=1 }

您永远不需要更新变量:您始终可以使用新值创建另一个变量。在你的情况下 let rec' = rec{field = 1}.

也许您担心性能和值被不必要地复制。那是编译器的工作,而不是你的:即使你在代码中声明了 2 个变量,编译器也应该只在内存中创建一个并就地更新它。

现在有时代码太复杂以至于编译器无法优化。您可以通过检查中间核心语言甚至最终汇编来判断。首先进行分析以了解哪些函数运行缓慢:如果它只是一个额外的 Int 或 Double,您不在乎。

如果您确实发现编译器未能优化的函数并且花费太多时间,那么您可以重写它以自行处理内存。然后,您将使用未装箱的向量、IO 和 ST monad,甚至语言扩展来访问本机机器级类型。