Sway 参考手册/函数
回想一下,我们评估了一系列表达式来找到直线上一点的y值
y = 5x - 3
给定一个x值
sway> var m = 5; INTEGER: 5 sway> var x = 9; INTEGER: 9 sway> var b = -3; INTEGER: -3 sway> var y = m * x + b; INTEGER: 42 sway> y; INTEGER: 42
现在,假设我们希望找到对应于不同x值的y值,或者更糟糕的是,对于不同直线上的不同x值。我们所做的所有工作都必须重复。函数是一种封装所有这些操作的方法,以便我们可以用最小的努力重复它们。
首先,我们将定义一个不太有用的函数,它计算给定斜率为 5、y 轴截距为 -3 和x值为 9(与上面完全一样)的y值。我们通过将函数包装在上面的操作序列周围来做到这一点。函数的返回值是最后一个被评估的值。
function y() { var m = 5; var x = 9; var b = -3; m * x + b; //this quantity is returned }
需要注意一些事项。关键字 function 表示正在发生函数定义。这个特定函数的名称是y。花括号之间的部分是调用函数时将被评估(或执行)的代码。此代码在之前不会被评估。
您可以将此函数复制并粘贴到 Sway 解释器中。如果你这样做,你会看到类似的东西
sway> function y() more> { more> var m = 5; more> var x = 9; more> var b = -3; more> m * x + b; //this quantity is returned more> } FUNCTION: <function y()>
注意,当输入不完整时,解释器提示会更改为more>当输入花括号闭合时,就会发生这种情况[1]。
一旦定义了函数,我们就可以重复找到y的值
sway> y(); INTEGER: 42 sway> y(); INTEGER: 42
y后面的圆括号表示我们希望调用y函数并获取它的值。
y函数按原样写,并不是太有用,因为我们不能用它来计算类似的东西,比如不同x值的y值。但在我们改进函数之前,让我们修改它,使其显示当前环境[2]。这可能有助于你理解在函数调用中发生的事情。当函数体正在执行时
function y() { var m = 5; var x = 9; var b = -3;
pp(this);
m * x + b; //this quantity is returned }
当我们调用y的新版本时,我们看到了它的当前环境,它具有b、x和m的绑定。
sway> y(); <OBJECT 2566>: context: <OBJECT 749> dynamicContext: <OBJECT 749> callDepth: 1 constructor: <function y()> this: <OBJECT 2566> b: -3 x: 9 m: 5 INTEGER: 42
变量b、x和m被称为局部变量,因为它们在函数体之外是不可见的。
一个好的函数的标志是它可以让你计算不止一件事情。我们可以修改我们的函数以接收我们感兴趣的x值。通过这种方式,我们可以计算多个y值。我们通过传递一个参数来做到这一点,在本例中,是x的值。
function y(x) { var slope = 5; var intercept = -3; return slope * x + intercept; }
我们通过在函数定义括号之间放置变量名称来为传入的值命名。在本例中,我们选择x作为名称。注意,由于我们传递了x,所以我们不再需要(也不想)定义x,因此我们将其删除。现在,我们可以计算无限多个x'的y值
sway> y(9); INTEGER: 42 sway> y(0); INTEGER: -3 sway> y(-2); INTEGER: -13
如果我们希望计算给定直线上不同x的y值怎么办?一种方法是将斜率和截距以及x一起传递进来
function y(x,slope,intercept) { return slope * x + intercept; } sway> y(9,5,-3); INTEGER: 42 sway> y(0,5,-3); INTEGER: -3
如果我们希望使用不同的直线进行计算,我们只需将新的斜率和截距与我们的x值一起传递进来。这当然按预期工作,但不是最好的方法。一个问题是我们必须不断输入斜率和截距,即使我们正在计算同一条直线上的y值。无论何时你发现自己在反复做着同样乏味的事情,请放心,有人已经想出了避免这种乏味的方法。因此,假设这是真的,我们如何自定义我们的函数,以便我们只需要为每条特定的直线输入一次斜率和截距?我们将探讨三种不同的方法。在进一步阅读时,如果你理解所有正在发生的事情并不重要。重要的是,你应该知道其他方法的存在,并了解每种方法的优缺点
由于创建函数是一项艰苦的工作(需要大量输入),而计算机科学家像躲避瘟疫一样躲避艰苦的工作,所以早些时候有人想出了编写一个自身创建函数的函数!太棒了!我们可以为我们的线问题做到这一点。我们将告诉我们的创意函数为特定的斜率和截距创建一个y函数!同时,让我们将变量名m和b分别更改为slope和intercept
function makeLine(slope,intercept) { function y(x) { slope * x + intercept; } y; }
makeLine函数创建了一个局部y函数,然后返回它。下一个版本是等效的
function makeLine(slope,intercept) { function y(x) { slope * x + intercept; } }
由于makeLine做的最后一件事是定义y函数,所以y函数被调用makeLine返回。
因此,我们的创意函数只是定义了一个y函数,然后返回它。现在,我们可以创建很多不同的直线
sway> var a = makeLine(5,-3); FUNCTION: <function y(x)> sway> var b = makeLine(6,2); FUNCTION: <function y(x)> sway> a(9); INTEGER: 42 sway> b(9); INTEGER: 56
注意,直线a和b如何记住创建它们时提供的斜率和截距[3]。虽然这绝对很酷,但问题是许多语言(包括 C 和 Java)不允许你定义创建其他函数的函数。幸运的是,Sway 允许这样做。
解决我们线问题的另一种方法是使用称为对象的东西。在 Sway 中,对象只是一个环境,我们之前已经见过它们。所以这里没有什么新鲜事,除了如何使用对象来实现我们的目标。在这里,我们定义了一个函数来创建并返回一个线对象。创建一个并返回对象的函数被称为构造函数。
function line(slope,intercept) { this; }
this变量始终指向当前环境,在本例中,它包含形式参数slope和intercept的绑定。通过返回this,我们返回line的环境,我们可以随时查看slope和intercept的值。为了证明 slope 和 intercept 存在,我们可以使用内置的漂亮打印函数pp
sway> m = line(5,-3); OBJECT: <OBJECT 231> sway> pp(m); <OBJECT 231>: context : <object 145> dynamicContext: <object 145> constructor: <function line(slope,intercept)> this: <object 231> intercept: -3 slope : 5 OBJECT: <OBJECT 231>
我们使用'.'(点)运算符访问对象中的变量
sway> m . slope; INTEGER: -3 sway> m . constructor; FUNCTION: <function line(slope,intercept)>
现在,我们修改y函数以接收一个线对象以及x,并使用点运算符提取线的斜率和截距
function y(line,x) { line . slope * x + line . intercept; }
在这种情况下,我们创建不同的线,然后将每条线传递给我们的新y函数
sway> var m = line(5,-3); OBJECT: <object 231> sway> var n = line(6,2); OBJECT: <object 256> sway> y(m,9); INTEGER: 42 sway> y(n,9); INTEGER: 56
这种方法的问题是我们将线对象与查找y值分开,但这两个概念密切相关。例如,假设我们有抛物线对象和线对象。我们的y函数对于抛物线对象将无法正常工作,即使抛物线上 (x,y) 点的概念与直线上的点一样有效[4]。
在面向对象的世界中,我们通过将对象和专门针对该对象工作的函数捆绑在一起来解决这个问题。在我们的例子中,我们使y函数成为线对象的一部分
function line(slope,intercept) { function y(x) { slope * x + intercept; } this; }
这与函数动态创建方法非常相似,但我们返回this,而不是绑定到y的函数。现在,我们通过线对象调用y函数。
sway> var m = line(5,-3); OBJECT: <object 231> sway> var n = line(6,2); OBJECT: <object 256> sway> m . y(9); INTEGER: 42 sway> n . y(9); INTEGER: 56
如果我们有一个抛物线对象,它将有自己的y函数,具有不同的实现。但是,我们以同样的方式调用它
sway> var p = parabola(2,0,0); OBJECT: <object 453>
sway> p . y(7); INTEGER: 49
这种方法在面向对象语言(如 Java)中得到支持。早期的方法(其中函数与对象分离)在过程式语言(如 C)中得到支持。
所有运算符都是函数,可以使用运算符语法调用。例如,以下表达式都对a和b的值求和
var sum = a + b; var sum = +(a,b);
相反,任何两个参数的函数都可以使用运算符语法调用。有时使用运算符语法可以使代码更清晰。让我们创建一个函数,将变量增加给定数量,类似于 C、C++ 和 Java 中同名的运算符。
function +=($v,amount) { $v = force($v) + amount; }
不要担心代码是如何工作的;只需注意+=函数有两个形式参数($v 和 amount),因此接受两个参数。我们可以调用+=使用函数调用语法增加变量
var x = 2; +=(x,1); inspect(x);
或者我们可以使用运算符语法
var x = 2; x += 1; inspect(x);
在这两种情况下,代码片段的输出都是相同的。
x is 3
使用运算符语法调用的函数与数学运算符具有相同的优先级,并且是左结合的。
- ↑ 在解释器中输入一个长结构很繁琐。在后面的章节中,我们将学习如何将代码存储在文件中,并让解释器执行该文件中的代码。如果需要更改代码,我们只需编辑文件。这样,我们就不需要从头开始将修改后的代码输入解释器。
- ↑ Sway 允许你重新定义变量和函数。
- ↑ 局部函数 y 的上下文是 makeLine 函数的局部环境。此环境包含 slope 和 intercept 的绑定。
- ↑ 这是一个尝试泛化的具体例子,以便我们的函数适用于所有概念有效的对象。