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 let
和 where
Haskell 中的绑定是递归的默认。所以当你写
let rec = rec { ... }
它试图定义一个循环数据类型,当您尝试对其求值时,它将永远循环(就像 let a = a
)。
没有真正的解决办法——这是语言的权衡。它使递归函数(甚至是纯值)更容易编写,噪音更小,但也意味着您不能轻易地根据自身多次重新定义 a
。
您真正能做的唯一一件事就是给您的值不同的名称——rec
和 rec'
是常见的做法。
公平地说 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,甚至语言扩展来访问本机机器级类型。
我想通过更改一个字段来更新记录语法,所以我做了类似的事情:
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 let
和 where
Haskell 中的绑定是递归的默认。所以当你写
let rec = rec { ... }
它试图定义一个循环数据类型,当您尝试对其求值时,它将永远循环(就像 let a = a
)。
没有真正的解决办法——这是语言的权衡。它使递归函数(甚至是纯值)更容易编写,噪音更小,但也意味着您不能轻易地根据自身多次重新定义 a
。
您真正能做的唯一一件事就是给您的值不同的名称——rec
和 rec'
是常见的做法。
公平地说 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,甚至语言扩展来访问本机机器级类型。