编程科学/封装操作
回想一下,我们评估了一系列表达式以找到直线上一点的y值
给定一个x值
sway> var m = 6;
INTEGER: 6
sway> var x = 9;
INTEGER: 9
sway> var b = -12;
INTEGER: -12
sway> var y = m * x + b;
INTEGER: 42
sway> y;
INTEGER: 42
现在,假设我们希望找到与不同x值对应的y值,或者更糟糕的是,对于不同直线上的不同x值。我们所做的一切工作都必须重复进行。函数是一种封装所有这些操作的方法,这样我们就可以用最少的努力重复它们。
首先,我们将定义一个不太有用的函数,它计算给定斜率为6、y截距为-12和x值为9时的y值(与上面完全相同)。
sway> function findY()
more> {
more> var slope = 6;
more> var x = 9;
more> var intercept = -12;
more> return slope * x + intercept;
more> }
FUNCTION: <function findY()>
请注意,解释器提示会更改为more
当输入不完整时。在上面的函数定义的情况下,当输入花括号结束符时会发生这种情况(1)。
一旦函数被定义,我们就可以重复找到y的值
sway> findY()
INTEGER: 42
sway> findY()
INTEGER: 42
这个函数不太有用,因为我们不能用它来计算类似的东西,比如不同x值的y值。一个好的函数的标志是它可以让你计算不止一件事。我们可以修改我们的函数来接收我们感兴趣的x值。我们通过传递一个参数来实现这一点,在本例中,x的值。
sway> function findY(x)
more> {
more> var slope = 6;
more> var intercept = -12;
more> return slope * x + intercept;
more> }
FUNCTION: <function findY()>
我们通过在函数定义括号之间放置变量名来给传递的值命名。在本例中,我们选择了x作为名称。请注意,由于我们正在传递x,因此我们不再需要(或不需要)x的定义,因此我们将其删除。现在我们可以计算无限多个x的y值。
sway> findY(9);
INTEGER: 42
sway> findY(0);
INTEGER: -12
sway> findY(-2);
INTEGER: -24
如果我们希望针对不同直线上的给定x值计算y值怎么办?一种方法是传递斜率和截距以及x
sway> function findY(x,slope,intercept)
more> {
more> return slope * x + intercept;
more> }
FUNCTION: <function findY()>
sway> findY(9,6,-12);
INTEGER: 42
sway> findY(0,6,-12);
INTEGER: -12
如果我们希望使用不同的直线进行计算,我们只需将新的斜率和截距以及x的值传递进来。这当然可以按预期工作,但这不是最好的方法。一个问题是我们必须不断输入斜率和截距,即使我们正在计算同一直线上的y值。每当你发现自己一遍又一遍地做同样的事情时,请放心,有人已经想出了避免这种特定乏味的办法。所以,假设情况确实如此,我们如何自定义我们的函数,以便我们只需为每条特定直线输入一次斜率和截距?我们将探讨三种不同的方法。在进一步阅读时,了解所有正在发生的事情并不重要。重要的是你知道其他方法存在,并了解每种方法的优缺点
在这一点上,你应该看到你可以创建函数,并且应该能够通过模式匹配来创建其他函数。例如,你应该能够定义一个函数来对给定数字进行平方
function square(x)
{
return x * x;
}
由于创建函数是一项艰苦的工作(很多打字),而且计算机科学家像躲避瘟疫一样躲避艰苦的工作,所以很早以前就有人想到了编写一个本身创建函数的函数!太棒了!我们可以为我们的直线问题做到这一点。我们将告诉我们的创意函数为特定的斜率和截距创建一个findY函数!
function makeFinder(slope,intercept)
{
function findY(x)
{
return slope * x + intercept;
}
return findY;
}
看看我们的创意函数是如何定义一个findY函数,然后将其返回的。现在我们可以创建一大堆不同的findY函数。
sway> var findYa = makeFinder(6,-12);
FUNCTION: <function findY(x)>
sway> var findYb = makeFinder(5,2);
FUNCTION: <function findY(x)>
sway> findYa(9);
INTEGER: 42
sway> findYb(9);
INTEGER: 47
请注意findYa和findYb是如何记住它们在创建时提供的斜率和截距的。虽然这无疑很酷,但问题是许多语言(包括C和Java)不允许你定义创建其他函数的函数。
解决我们的直线问题的另一种方法是使用一个叫做对象的东西。在Sway中,对象只是一个环境,我们之前见过这些。所以这里没有什么新东西,只是在如何使用对象来实现我们的目标方面有所不同。在这里,我们定义一个创建函数(称为构造函数),它创建并返回一个直线对象。
function line(slope,intercept)
{
return this;
}
this变量始终指向当前环境,在本例中,包括斜率和截距。通过返回this,我们返回此环境,并且我们可以随时查找斜率和截距的值。为了证明斜率和截距的存在,我们可以使用内置的bindings函数
sway> lineA = line(6,-12);
sway> bindings(lineA);
OBJECT 231:
context : <object 145>
dynamicContext: <object 145>
constructor: <function line(slope,intercept)>
this: <object 231>
intercept: -12
slope : 6
SYMBOL: :true
我们使用 '.'(点)运算符访问对象中的变量
sway> lineA . slope;
INTEGER: 6
sway> lineA . constructor;
FUNCTION: <function line(slope,intercept)>
现在我们修改我们的findY函数,使其接收一个直线对象以及x,并使用点运算符提取直线的斜率和截距
function findY(line,x)
{
return line . slope * x + line . intercept;
}
在这种情况下,我们创建不同的直线,然后将每条直线传递给我们的新findY函数
sway> var lineA = line(6,-12);
OBJECT: <object 231>
sway> var lineB = line(5,2);
OBJECT: <object 256>
sway> findY(lineA,9);
INTEGER: 42
sway> findY(lineB,9);
INTEGER: 47
这种方法的问题是我们将直线对象与查找y值分开了,但这两个概念密切相关。例如,假设我们有抛物线对象和直线对象。我们的findY函数将无法为抛物线对象正常工作,即使抛物线上(x,y)点的概念与直线上的点一样有效(2)。在面向对象的世界上,我们通过将对象和专门对该对象起作用的函数捆绑在一起解决了这个问题。在我们的例子中,我们将findY函数作为直线对象的一部分
function line(slope,intercept)
{
function findY(x)
{
return slope * x + intercept;
}
this;
}
这与动态函数方法非常相似,但我们返回this而不是findY。现在我们通过直线对象调用findY函数。
sway> var lineA = line(6,-12);
OBJECT: <object 231>
sway> var lineB = line(5,2);
OBJECT: <object 256>
sway> lineA . findY(9);
INTEGER: 42
sway> lineB . findY(9);
INTEGER: 47
如果我们有一个抛物线对象,它将有自己的findY函数,但实现方式不同。但是,我们以同样的方式调用它
sway> var parabolaA = parabola(2,0,0);
OBJECT: <object 453>
sway> parabolaA . findY(7);
INTEGER: 49
这种方法在面向对象的语言(如Java)中得到支持。早期的方法(函数与对象分离)在过程式语言(如C)中得到支持。
(1) 在解释器中键入一个长结构很乏味。稍后我们将学习如何将代码存储在文件中,并让解释器执行该文件中的代码。如果我们需要更改代码,我们只需编辑该文件即可。这样,我们就不需要从头开始将修改后的代码键入解释器。
(2) 这是一个试图泛化的具体例子,以便我们的函数适用于所有函数概念有效的对象。