热替换的好处就不提了,至少是有用处的。如果没有
Erlang
,我想迟早也会去关注这个话题。尤其是在
Tk
中绑定事件处理函数时或者在调试的时候,根据不同的需要,可以动态的改变代码功能,这显然非常方便。但是如果实现的过程本身可能破坏原来代码的结构,我想这就有点得不偿失了。最起码的,应该有一套方便并且可遵循的规则。下面就两种办法进行分析。
先来设定一个最基本的实验环境:一个用来显示结果的文本框和一个用来激发事件的按钮,足矣。需要达到的目的是,在运行过程中,动态改变按钮的绑定处理函数。
第一种做法
--
嵌入
source
:
Tcl
的代码集成,包括
package
等,多是建立在
source
和
load
的基础上的,说白了
package require
不过也是在
pkgIndex.tcl
中找到相应的文件并
source
之。所以第一种思路是在按钮的绑定函数中
source
文件,主要的代码片段如下:
text .t
button .bt -text test -command {
source
b.tcl
}
pack .t .bt
我不得不将
source
放在函数中的原因在于,必须在每次运行时检测文件,以确保运行了最新修改过的代码。其中,
b.tcl
的代码为
.t insetr end “1\n”
用来在文本框中新插入一行,内容为
1
。如果将
1
改为
2
,程序将自动变换为插入内容为
2
的一行。但是这里有两个问题存在,首先这种检测很多时候是多余的,我们并不需要修改代码;第二,
source
本身是对文件的读入操作,这不可避免地引入了
IO
,也就是说影像了性能。这种热替换方式,不大适合与巨大的事件处理函数,或者可能遭遇频繁调用的情况。
第二种做法
–- socket
指令
:
在修改完
b.tcl
之后,需要有人告诉运行程序一声,“喂,你的按钮该换代码了!”,接着主程序兴冲冲地去找按钮处理函数的源代码。这里讲的其实是一个进程间通信的问题,于是选择了
socket
。我把这个过程写了两个文件,一个是名为
hot.tm
的包,提供了两个主要函数:
init
,用来指定可以被替换的函数及其相对应的文件;
update
,用来具体实施热替换。其实替换的过程也是一个
source
,不同之处在于
source
的内容是一个函数,而非第一种情况里函数中的一段代码,这样新的函数将覆盖之前的版本。这是
a.tcl
的代码需要有所修改:
lappend auto_path [file dirname [info
script]]
package require hot
hot::init b b.tcl
text .t
button .bt -text test –command b
pack .t .bt
在
hot
的
namespace
中,维护着一个函数名到源文件的
array
,在初始化时需要指明哪些函数是可以被热替换的。我可不想随便哪个函数都可以被替换,这就乱套了。
另一个文件时
hotc.tcl
,用来在修改完代码之后通知运行程序来进行热替换,当然这个过程只能依靠人工来完成了。比如在这里需要替换
b
函数,那么就执行一下
hotc.tcl b
。
最后我想,这应该是一个比较可以被接受的方法了。其实即使是标榜着热替换的
erlang
,也需要在修改代码之后
compile
的,这就好比执行了
hotc.tcl
,只不过
erlang
的做法是语言级的。
代码下载