动态简介
这些函数是一个更高级别的函数 Manipulate (操作)的基础,这个函数提供了一个简单然而强有力的方法来创建大量的交互式的例子、程序和演示项目,全部都是在一个方便的,尽管相对刻板的结构中. 如果这个结构能解决手头的问题,你不需要看 Manipulate 以外的内容,你也不需要读这个教程. 但是,如果你希望建立范围更广泛的结构,包括复杂的用户界面,那么请继续阅读这个教程.
第一个输出仍然显示的是当 x 是5时的值,尽管现在的输入是7. 如果你想看到你所做的事情的历史,这当然是很有用的. 但是,你也许经常希望有一个本质上不同的输出,它自动更新以致总是反映出它当前的值. 这一种新的类型的输出由 Dynamic 提供.
对于能放到一个动态输出里的值的类型是没有任何限制的. 仅仅因为 x 最初是一个数字并不意味着它在接下来的计算中不能成为一个公式或甚至一个图形. 这也许看起来是一个简单的特性,但它是一组非常强大的交互式功能的基础.
Dynamic[expr] | 一个作为 expr 的动态更新了的当前值而显示的对象 |
Dynamic 经常和诸如滑块以及复选框这样的控件连接在一起使用. Wolfram 语言中提供的全部控件在 "控件对象" 中进行了讨论;这里用滑块来阐明它们是如何工作的. 和其它的控件一起使用 Dynamic, 其原理基本上是一样的.
一个滑块由计算 Slider (滑块)函数来创建,其中第一个自变量是位置,可选的第二个自变量指定范围和步长,默认的范围是从0到1,默认的步长是0.
Dynamic 和如同 Slider 的控件结构在很多方面就像 Wolfram 语言中的任何其它函数一样. 它们能在一个输出, 表格,和甚至排版的数学表达式里的任何地方出现. 这些函数出现时,总是伴随着动态显示或与它们相连接的表达式或变量的当前值的实时改变这样的行为. Dynamic 是一个简单的建筑块,但是 Wolfram 语言其余的部分把它变成一个灵活工具来创建灵巧的、敏捷的、常常又是有趣的小巧互动显示.
注意最后一个例子和 Manipulate 的输出相似. 这不是巧合,因为 Manipulate 实际上产生一个 Dynamic、控件和格式构件的组合,这和你自己用这些低级别的函数所能做的事情没有根本上的不同.
如果你让这两个输出在屏幕上都看得见,并且拖拉任何一个滑块,你将注意到它们在互相通讯. 在一个例子里移动滑块,另一个例子也移动. 这是因为你在两个例子中都用了全局变量 x. 尽管这在某些情况下是会很有用的,但是在绝大多数情况,你可能更乐意让这两个滑块独立地移动. 解决的办法是一个叫做 DynamicModule 的函数.
DynamicModule[{x,y,…},expr] | |
DynamicModule[{x=x0,y=y0},expr] | 指定 x、y … 的初始值 |
对 Dynamic 对象变量局部化和初始化.
多个 DynamicModule 能被放到一个单一的输出中,并且在输出中,它们分别保持与各自区域相联系的变量的值:
你也许很想用 Module 来代替 DynamicModule,实际上这在第一眼看上去好像是可行的. 但是,这不是一个好的主意,有几个原因,这在 "高级动态功能" 里有详细的讨论.
DynamicModule 在前端工作,不在内核里工作. 它在运算时保持不变,当它被格式化成输出时,它创建一个隐藏的对象,嵌在处理局部化的输出表达式里. 只要输出的那个空间是存在的(也就是,没有被删除),那个代表 DynamicModule 的隐藏的对象将保留变量的值,允许它们接下来在 DynamicModule 作用域之内对 Dynamic 表达式的计算中使用.
如果你保存了一个包含 DynamicModule 的笔记本,关上那个笔记本,之后在一个新的 Wolfram 系統中把它重新打开,所有局部变量的值将仍旧被保留,在 DynamicModule里的滑块将会在同样的位置. 这种情况在滑块和全局变量相连接(像这个教程中最前面的例子)的时候,或者在滑块和 Module 里而不是 DynamicModule 里的局部变量相连接的时候,将不会发生. 这样的变量把它们的值存储在当前的 Wolfram 语言的内核会话中,一旦你退出 Wolfram 系統它们就遗失了.
除了把变量局部化于特定的输出区域之外,DynamicModule 提供了相关选项以便在包含一个 DynamicModule 的表达式被打开时自动初始化函数定义,并且在这个表达式被关掉或被删除时清除其值. 更多的细节在 DynamicModule 中能找到.
动态连接在默认下是双向的. 和一个变量相连接的多个滑块是一起移动的,这是因为它们都反映和控制同一个变量的值. 当你拖拉一个滑块的滑标时,系统将构造并计算形式为 expr=new 的表达式,这里 expr 是赋予 Dynamic 的第一自变量的表达式, new 是建议的新值,取决于你把滑块的滑标拖拉到哪里. 如果赋值成功,那么新值被接受. 如果赋值失败,滑块将不移动.
你可以在 Dynamic 的第一自变量中保留一个任意的表达式,但通过选用第二自变量来改变动态执行的计算. 这是指定更新第一自变量里的变量值的“反函数”的一个方便的办法. Wolfram 语言不试图通过 Dynamic 的第一自变量自动地推导出这样的反函数;你自己必须提供一个.
Dynamic[expr,f] | 在交互式改变或编辑 val 的过程中连续计算 f[val,expr] |
为了对跟踪行为有完全的控制,可以另外指定别的函数,在鼠标点击滑块滑标的开始、中间和结束时调用. 如果你熟悉普通的用户界面编程,那么你将看出这些正是单独的鼠标按下,鼠标拖拉和鼠标释放事件的高级别事件函数.
Dynamic 的第二个自变量也能让你限制一个滑块的移动从而实现几何约束效果.
你只能将这个 Slider2D (二维滑块)的滑标沿着一个圆移动:
在一般的输出中你看不见 Dynamic,这是因为,在前端的格式化的显示中,Dynamic[x+y] 由这么一个对象代表,这个对象包含一个没有被计算的输入 (x+y) 的复本,但是以被计算的表达式的值显示. Dynamic 的封装仍然在输出中存在,但是它是不可见的.
因为 Dynamic 完全在前端完成它的工作,你不能把它用在需要使用一个表达式的值才能工作的函数中.
Plot 这个命令需要有具体的 x 的数值才能画出一个图形,但是在被画图的函数里的 Dynamic[x] 在内核中不会被计算成任何内容. 它只保持为 Dynamic[x] 不变,使得 Plot 命令不能作出任何有意义的事情.
另一个看这个事情的方式是,Plot 命令里面的表达式并不在输出的任何地方直接地出现. Dynamic 是一个在前端工作的格式化函数,不是在内核中,所以如果它被使用在永远不会被放置在输出中的地方时,那么就很有可能是一个错误.
那是因为当用 Dynamic 封装 Slider[x] 时,在计算它的内容时,x 的值被代入,结果只是其第一自变量,滑块,为一具体数字,没有跟踪剩下的变量名. 这种情况下的滑块是一个静态滑块的动态显示.
所需要的是一个静态滑块,其中包含了一个对变量值的动态参照. 对于控件,有一个简单的规则来决定在什么地方放 Dynamic. 任何控件函数,例如 Slider(滑块)、Checkbox (复选框)或 PopupMenu(弹出菜单)的第一个自变量都几乎总是 Dynamic[var].
除了在这些情况下 Dynamic 在某一特定的位置不工作以外,对于把 Dynamic 放在哪里经常有很大的灵活性. 它经常作为一个输入表达式的最外层函数来使用,但这并不是绝对必需的,在更复杂的应用中,Dynamic 通常用在表达式里更深的一层,甚至是可以嵌套的.
当 x 被改变时,第一个例子给内核送去一个单个的要求来获得 Table[x,{i,10}] 的值,而第二个例子给内核送去10个单独的要求来得到 x 的数值. 也许看上去第一个例子明显地效率更高,在这个情况下它确实是的. 但是,你也应该避免另一个极端:即在一单个的 Dynamic 中包括太多的东西,这也会使得效率降低.
拖拉滑块,你会发现第一个选项卡里的 x 的值更新得相当快. 在绝大多数的计算机里这基本上是瞬时的. 但是,第二个选项卡里的更新更缓慢一些. 为了保持最新的值,每一个单独的 Dynamic 表达式都(非常仔细地)跟踪究竟什么时候需要被重新计算. 在第二个选项卡里,输出强迫整个表达式 {x,y},包括那个大的,慢的三维图形,每当 x 的值变化时都被重新计算. 通过在第一个选项卡里使用两个单独的 Dynamic 表达式,你使得 x 的值能被更新而不需要重新计算 y,它事实上还没有被改变. (你最好在继续往下读之前删除最后一个输出,因为只要它是在荧屏上可见,它就会减慢任何包含全局变量 x 的例子.)
对于每一种情况下应该把 Dynamic 放在什么地方,很难作出一个概括声明,但是一般来说如果你是在构造一个大的,复杂的输出,其中只有小的部分会改变,那么 Dynamic 很可能应该仅仅用于那些部分. 另一方面,如果所有的或绝大多数的输出将会随一个单一变量值的改变而变化,那么很可能最好是将 Dynamic 用于整个输出.
画图命令中像 PlotPoints (画点)的选项不能把 Dynamic 用在右边,因为画图命令在图形产生之前需要知道一个具体的数值. 记住 Dynamic 具有延迟计算直到表达式到达前端的效果,对于PlotPoints,这已太晚了, 因为它立即需要那个值. 然而,对于在前端完成工作的函数的选项,则通常,并且不无益处地,接受 Dynamic 作为其选项值.
把 Dynamic 放在选项值里有两个可能的优点. 第一,假设动态重新产生的表达式非常大,比如说,整个文档. 每一次字体大小改变时都把它从内核重新传送到前端是低效率的,如果把 Dynamic 用于整个表达式,则这种传送是必需的.
第二,一个 Dynamic 表达式的输出是不可编辑的(因为它会在任何时候被重新产生),这使得第一个例子的输出是不可编辑. 但是第二个例子中的文字可以自由地编辑, 因为它是普通的静态输出:仅仅是选项值是动态的.
动态选项值也能在 选项设置 中设置. 它们允许在单元、笔记本或全局的级别上,以及在样式表里. (但是注意,如果你把一个动态选项值设置在一个会被很多单元继承的位置,例如在一个样式表里,那么它对工作性能可能会造成很大的影响.)
你也能通过 SetOptions (设置选项)来设置动态选项值:
如果你不小心,你可以很容易地把 Dynamic 放到一个无限循环中.
因为在无限循环的每一个周期后更新输出并重画屏幕,这一功能实际上是相当有用的. 一般来说,系统会对打字、计算等等保持反应,即使在无限更新的 Dynamic 急速进行时.
制造一个自己触发,在某处停止变化的 Dynamic 也是有用的.
如果你有一个 CPU 监视器正在运行,你就会看到当滑块在下降时在 CPU 上有一个小的负载(主要是为了重新画荧屏的),但是一旦它到达零,负载降低到零. 动态跟踪系统注意到 x 的数值没有变化:因此,直到某人再把 x 的数值改变之前(例如,当你点击滑块时)进一步的更新是没有必要的. "高级动态功能" 更详细地描述了动态跟踪系统是怎样工作的.
看一下那个滑块列表的 InputForm 就会明白这个问题:
这能用 /. 来完成,或者用下面这个看起来有些奇特但是符合语言习惯的形式来做:
这个输出表明, Dynamic 事实上能和部分提取语句一起很好地工作,这是一个非常有用的属性.
将 Dynamic 用于一个需要很长时间,或者是超过几秒钟才能完成计算的函数不是一件好事情.
如果你计算这个例子,你将必须等待大约5秒钟才会看见输出 $Aborted:
在等待 Dynamic 的输出计算的过程中,前端是冻结的,并且打字和其它的动作都是不可能的. 因为更新普通的动态输出锁住前端,所以重要的是把放在 Dynamic 里面的表达式限制为那些能相对迅速完成的计算(最好是在一秒钟左右). 幸运的是计算机以及 Wolfram 语言都很快,所以范围广泛的函数,包括复杂的二维和三维的图形,能轻易地转瞬间被计算.
为了永远地避免锁住前端,动态计算被内部地封装在 TimeConstrained (时间约束)里,带有一个超时的数值,默认下是5秒钟. (这能用 DynamicEvaluationTimeout 选项来改变. )在某种极端情况下,TimeConstrained 会失败以放弃计算,在这种情况下,几秒钟之后,前端会显示出一个对话框允许你终止动态更新直到引起问题的那个输出被删除为止.
幸运的是,如果你需要在 Dynamic 里有计算慢的内容,在这种情况下还有一个可选的办法. SynchronousUpdating->False 这个选项允许动态以一种不会锁住前端的方式来计算. 在对这样的异步的 Dynamic 的计算过程中,前端照常运行,但是主要的 Shift+Enter 计算队列忙于计算 Dynamic,所以进一步的 Shift+Enter 计算将等到 Dynamic 结束为止. (正常的同步 Dynamic 计算不会干预 Shift+Enter 计算.)
"高级动态功能" 给出了关于同步和异步动态计算之间区别的更多的细节. 一般地,你不应该计划使用异步的动态计算,除非是绝对必要的. 它们更新不是那么迅速,并且能和控件以及其它同步计算以一种非常令人吃惊,尽管技术上不是错误的方式进行互相作用.