AFAIK,这是LISP方言的标志性特征:例如,假设我们启动了一个正在进行的LISP程序,该程序重复调用一些名为func
的函数。然后我们可以进入lisp REPL,更改func
的定义,然后在下一次程序调用{{1}}时,程序的行为将被适当地改变。< / p>
我相信这个属性被称为后期绑定,虽然我不确定我是否正确。
后期绑定的好处是它允许您在程序仍在运行时重构甚至完全更改程序。这在生物学上类似于这样一个事实,即随着年龄的增长,我们体内的几乎每个细胞都会在足够长的时间范围内被新的细胞取代(当然我们从未注意到)。它可以使系统非常灵活。
问题:有没有办法在Haskell中做类似的事情?如果Haskell不允许这样做,那么它是否有充分的理由(即,是否进行了一些更大的权衡,使得一切都值得)?
答案 0 :(得分:5)
理解声明性编程的一个好方法是将其视为永恒。所以没有“下次程序调用{{1}}”时。如果程序在某种低级操作意义上的“下一次”表现不同,那将违反纯度。当我们走出程序时,保持我们的FP彩色镜片牢固到位,我们注意到你不能“改变func
的定义”,而你正在做的是制作一个具有不同定义的新程序func
。所以当你“改变func
的定义”时,你所说的真正在做的就是放弃当前的程序并运行一个新程序。 Haskell库就是这样运作的,例如halive和dyre。
func
monad让我们模仿“之前”和“之后”,因此在其中,我们可以谈论“改变”某些事物。更改“the”程序仍然具有与上述相同的问题,但我们当然可以拥有一个包含我们更新的一堆代码的引用单元。实际上,这就是Lisp的(天真实现)中发生的事情。您实际上可以手动执行此操作,例如定义一个IO
或IORef
来保存您想要更改的功能并根据需要进行更新。现在的问题是你通常想要更新到全新的功能,所以我们需要有一种方法来描述新功能或者动态加载功能。前者对应于有一个可用的解释器,名义上是hint和mueval所做的(虽然它们实际上更像下面的工作)。执行后者是动态代码加载,像plugins,rts-loader和dynamic-loader这样的库可以做到这一点。
或者,您可以采用视图(或实际构造它),使代码的某些部分作为协同进程运行。此时,您可以使用标准IPC机制。在此上下文中“调用函数”仅仅意味着发送消息。
最终,这些都没有提供像Lisp这样的体验。 Haskell语言本身不提供“地点”的机制或概念,其中“存储”定义然后可以更新。同样,在概念上,在Lisp中,每个定义都存储在一个可变单元中,并且每个函数调用首先取消引用该可变单元以获得当前定义。结果是我提到的每个库和技术都要求您提前计划更改点或要重新加载的内容,或者至少 要重新加载。没有“附加”到正在运行的Haskell进程或“破解”到调试器中的概念。但从技术上讲,使用dyre技术可以重新启动一个用完全不同的语言编写的可执行文件,更不用说任意改变的Haskell可执行文件了。
答案 1 :(得分:0)
据我所知,除非你把这个函数包装在IO或状态monad中,否则haskell不能支持这个。
但是在一个纯粹的程序中重新分配func
的定义会破坏引用透明度 - 这是使得haskell成为一种安全语言的事情之一。此外,破坏参照透明度会使懒惰评估变得不可能,因为评估函数 - 在更新之前或之后会产生两个不同的结果并使程序不确定。
现在,如果您查看GHCi
,那么您会看到可以重新分配变量 - 如果您尝试
$ > ghci
...
Prelude > let a = (+)
Prelude > let a = (*)
有效,但let
IO
绑定存在于notifyDataSetChanged()
我认为 - 这样可以产生重新分配等副作用。