编程科学/最简单的事
如果你学习过CME的第4章,你就会学习到对简单多项式函数(称为项)进行微分的幂法则。一项具有以下形式
其中a被称为项的系数,n被称为项的指数。
以下是项的幂法则
例如,考虑多项式
如果幂法则正确,则导数应该为
让我们测试一下。我们首先定义一个函数来表示多项式
function y(x) { 2 * (x ^ 3); }
和以前一样,因变量成为函数名,自变量成为形式参数。
在我们继续之前,现在是时候学习关于使用Sway与文件,因为在解释器中输入函数定义可能相当痛苦。
因此,在本册书的其余部分,即使显示了与解释器的交互,你也应该将Sway程序存储在文件中,并从文件中运行Sway程序。
导数,或可以用这个函数来表示
function dy/dx(x) { 6 * (x ^ 2); }
有没有办法测试幂法则是否正确?回想一下我们上一章中修改过的比率函数
function ratio(x,dx,y) { var dy = y(x + dx) - y(x); dy / dx; }
如你所知,它计算的比率是给定x的导数的近似值。请注意,我们已将形式参数的名称从w更改为x,dw更改为dx,h更改为y,以反映我们现在正在处理多项式的事实。[1]
我们认为ratio(对于较小的dx)会产生一个数字,该数字应该接近函数dy/dx产生的数字。
让我们定义一个测试函数
function test(x) { var dx = 0.000001; println("for x = ",x); inspect(ratio(x,dx,y)); inspect(dy/dx(x)); println(); }
给定x的值,测试函数将打印出两个结果。现在来说一些命名法。当我们使用ratio函数时,我们是在数值地求导数。也就是说,我们使用dx的一个小数字,找到相应的dy,然后计算比率。另一方面,使用幂法则(如函数dy/dx所体现的),我们符号地求导数,因为我们从未真正计算过实际的比率。
sway> test(10); for x = 10 ratio(x,dx,y) is 600.00005965 dy/dx(x) is 600 sway> test(20); for x = 20 ratio(x,dx,y) is 2400.0001231 dy/dx(x) is 2400 sway> test(30); for x = 30 ratio(x,dx,y) is 5400.0001837 dy/dx(x) is 5400
对于这个测试,我们看到我们的数值解和符号解之间有很好的一致性。
显然,符号解是首选,因为
- 它是精确的
- 它更容易计算
你可能会问自己,我们能编写一个程序来符号地求导数吗?
我们方法的缺陷
[edit | edit source]如果我们能使用符号方法来求导数,而不是使用基于比率的数值方法,那就太好了。问题是,我们用来表示一项的函数
function y(x) { 2 * (x ^ 3); }
的系数为2,指数为3,是硬编码的。如果我们得到了这样的函数,但不知道内部细节(例如硬编码指数的值),我们就无法创建导数函数,因为我们需要知道指数的值才能做到这一点,并且没有办法获取它。我们需要想办法从函数中提取指数,以便我们可以使用它来构建导数函数。
下一节将向你展示一种从函数中提取组件的方法。该方法使用对象。
如果你不知道什么是对象,或者如何在Sway中创建对象,请阅读使用对象的相关内容,这些内容位于Sway 参考手册中。
好的,现在你已经熟悉了对象,我们可以开始使用对象编写一个程序来符号地求导数。
多项式对象和幂法则
[edit | edit source]我们不使用函数来表示多项式y,而是使用对象。通过学习参考手册,你应该知道对象只是一个环境,环境只是一个变量及其值的表格。你应该还知道,你使用函数来创建对象,而创建对象的函数通常称为构造函数。最后,你应该知道,为了定义一个构造函数,你需要让该函数返回预定义变量this。
以下是一个简单多项式对象的构造函数。我们将构造函数命名为term
function term(a,n) { function value(x) { a * (x ^ n); } this; }
请注意,构造函数包含一个内部函数,用于在给定x的情况下计算y的值,就像之前一样。我们使用点运算符来调用该函数。
sway> var y = term(2,3); OBJECT: <OBJECT 1671> sway> y . value(4); INTEGER: 128 sway> y . value(5); INTEGER: 250
实际上,确实是128,而确实是250。
现在是重点!将多项式项存储为对象而不是函数,使我们能够提取系数a和指数n
sway> y . a; INTEGER: 2; sway> y . n; INTEGER: 3;
我们甚至可以漂亮地打印对象y来查看其所有字段
sway> pp(y); <OBJECT 2561>: context: <OBJECT 749> dynamicContext: <OBJECT 749> callDepth: 1 constructor: <function term(a,n)> this: <OBJECT 2561> value: <function value(x)> a: 2 n: 3 OBJECT: <OBJECT 2561>
我们看到,在其他字段中,a和n的值是正确的。稍后,你将了解预定义字段的含义和用途:context、dynamicContext和constructor。你已经知道this字段了。
我们希望访问a和n,因为幂法则需要它们来计算导数多项式。现在我们可以写出一个表达式,让我们计算导数
sway> var coeff = y . a; sway> var exp = y . n;
sway> var z = term(coeff * exp,exp - 1); OBJECT: <OBJECT 2783>
sway> z . a; INTEGER: 6
sway> z . n; INTEGER: 2
sway> z . value(4) INTEGER: 96
当然,我们可以编写一个函数来完成这个任务
function powerRule(t) { var coeff = t . a; var exp = t . n; term(coeff * exp,exp - 1); }
请注意,powerRule函数以一个term对象作为参数,并返回一个表示导数的新term对象。这是因为新项的系数为a * n指数为n - 1正如幂法则所规定的一样。让我们检查一下,确保我们的幂法则按预期工作
sway> var y = term(2,3); sway> var z = powerRule(y); sway> y . value(4); INTEGER: 128 sway> z . value(4); INTEGER: 96
确实如此!
此外,漂亮地打印z会显示出a和n的正确值
sway> pp(dy/dx); <OBJECT 2922>: context: <OBJECT 749> dynamicContext: <OBJECT 2808> callDepth: 2 constructor: <function term(a,n)> this: <OBJECT 2922> value: <function value(x)> a: 6 n: 2 OBJECT: <OBJECT 2922>
你可能没有注意到,我们有点缺乏对称性。虽然项对象有一个用于计算其值的内部函数,但我们使用外部函数powerRule来计算其导数。当我们使用对象编程时,我们尽可能使用内部函数。因此,让我们重写term函数,以便它可以计算自己的导数
function term(a,n) { function value(x) { a * (x ^ n); } function diff() { term(a * n,n - 1); } this; }
我们将这个新的内部函数命名为diff,以表示取我们对象的微分。请注意,我们在diff函数内部不再使用变量coeff和exp,而是直接使用a和n。
最后,我们在term中添加了第三个内部函数。它用于以比使用pp函数更简洁的方式可视化我们的对象。按照惯例,我们将这个函数称为toString
function term(a,n) { function value(x) { a * (x ^ n); } function diff() { term(a * n,n - 1); } function toString() { string(a) + "x^" + string(n); } this; }
toString函数将系数和指数都转换为字符串,然后将这些字符串连接在一起,形成一个项的字符串表示形式
sway> y = term(2,3); sway> z = y . diff();
sway> y . toString() STRING: "2x^3"
sway> x . toString() STRING: "6x^2"
请注意,使用toString函数来提取项的系数和指数有多么容易。另一种表达toString函数的方法是利用这样一个事实:如果你添加一个字符串和一个数字(字符串位于加号的左侧,数字会自动转换为字符串),再加上Sway从左到右组合数学运算符的事实,我们可以删除n的字符串转换
function toString() { string(a) + "x^" + n; }
如果我们使用空字符串来开始表达式,我们也可以删除第一个字符串转换
function toString() { "" + a + "x^" + n; }
一个完整的对象系统
[edit | edit source]从现在开始,我们将开发一个系统来寻找各种数学对象的导数(最终还有积分),这些对象的类型只受我们的想象力和注意力范围的限制。我们系统中的每个对象都将执行(至少)三项操作。首先是在给定位置计算其值。第二是计算其导数(最终还有其积分)。第三是计算其可视化。因此,我们所有的数学对象都将具有值、diff和toString内部函数或方法。 [2]
即使是最简单的现实世界数学对象,例如数字和变量,也需要重新创建为 Sway 对象。这样,我们就可以随时获取对象的导数,而不必先询问获取导数是否有意义。
问题
[edit | edit source]所有公式均使用小学优先级编写。
1. 为什么 Sway 实现的 有括号2 * (x ^ 3)?
2. 使用powerRule函数,可以对导数求导吗?解释一下。
3. 编写一个函数,使用 power rule 函数回答 Thompson 第 58 页上的以下问题:1、2、4、6、7。将你的函数命名为p58。你的函数应该计算然后打印问题的答案。
4. 使用纸笔完成 Thompson 第 58 页上的练习 3、8、9 和 10。
脚注
[edit | edit source]- ↑ 通常,形式参数的名称并不重要,因此我们可以保留ratio函数不变,它仍然可以完全正常工作。
- ↑ 在面向对象编程(另一种说法是使用对象编程)的世界中,这些内部函数被称为方法。从现在开始,我们将使用术语方法。请记住,方法只是一个内部函数。