Sway 参考手册/变量和环境
假设你在街上发现一个信封,信封正面印着numberOfDog'sTeeth这个名字。假设你打开信封,里面是一张纸,上面写着数字 42。你会从这种遭遇中得出什么结论?现在假设你继续走,发现另一个标有meaningOfLifeUniverseEverything的信封,你再次打开它,发现一张写着数字 42 的纸条。在路途更远的地方,你又发现了两个信封,分别标有numberOfDotsOnPairOfDice和StatuteOfLibertyArmLength,两者都包含数字 42。
最后,你发现一个标有sixTimesNine的信封,你再次在里面发现数字 42。此时,你可能在想“有人对数字 42 有着奇怪的偏爱”,但随后你脑海中某个昏暗角落里的乘法表开始对你大喊大叫,说“54!是 54!”。经过一段令人尴尬的长时间后,你意识到 6 * 9 不是 42,而是 54。所以你划掉了最后一个信封中的 42,改写成 54,然后把信封放回原处。
这个奇怪的小故事,信不信由你,对编写人类和计算机都能理解的程序具有深远的影响。对于编程语言来说,信封是变量的隐喻,变量可以被认为是内存中某个位置的标签,原始值可以驻留在该位置。在许多编程语言中,可以更改该内存位置的值,就像更换信封的内容一样[1]。变量是我们第一次接触到一个称为抽象的概念,这个概念是整个计算机科学的基础[2]。
很可能你之前遇到过“变量”这个词。考虑一条特定直线的代数方程的斜截式
你可能可以从这个方程中看出这条直线的斜率是 2,它与y轴的交点是 -3。但是字母y和x实际上起什么作用呢?x和y是占位符,代表这条直线上任何可想而知的点的x坐标和y坐标。如果没有占位符,这条直线将不得不通过列出直线上的每个点来描述。由于有无限多个点,显然不可能提供一个详尽的列表。正如你在代数课上学到的那样,占位符的通用名称是变量。
可以将上述直线推广,得到一个描述每条直线的方程[3]。
在这里,变量m代表斜率,b代表y截距。显然,这个方程不是由计算机科学家想出来的,因为一条基本规则是为变量选择好名字,例如,s代表斜率,i代表截距。但遗憾的是,由于历史原因,我们仍然使用m和b。
术语“变量”也在大多数编程语言(包括 Sway)中使用,并且该术语具有大致相同的含义。不同之处在于编程语言使用信封隐喻,而代数则不使用(这种差异纯粹是哲学上的,现在还没有必要讨论)。假设你发现三个信封,分别标有m、x和b,在这些信封中你分别找到了数字 6、9 和 -12。如果你被要求制作一个y信封,你应该在里面放什么数字?如果上一个故事中sixTimesNine信封中的数字 42 没有让你困扰(例如,你内部的乘法表无处可寻),那么你可能需要一些帮助才能完成你的任务。我们可以让 Sway 使用以下对话框计算这个数字
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
在 Sway 中使用var关键字创建变量[4]。
创建变量被称为声明或定义。在 Sway 中,var关键字后面的标记是正在定义的变量,等号后面的值是变量的初始值。最后一个定义表明初始值可以是一个表达式,该表达式引用其他变量。当被要求计算包含变量的表达式的值时,Sway 解释器会转到这些信封(可以这么说)并检索存储在那里的值。还要注意,Sway 要求使用乘号将斜率m乘以x值。在代数方程中,省略了乘号,但在这里是必需的。
以下是一些使用var关键字创建变量的更多示例
sway> var dots = 42; INTEGER: 42
这种交互定义了一个名为dots的变量,其值绑定到数字 42。解释器对变量声明的响应是显示绑定到该变量的值。
sway> var bones = 206; INTEGER: 206
sway> dots; INTEGER: 42
sway> bones; INTEGER: 206
sway> var CLXIV = bones - dots; INTEGER: 164
在变量声明之后,变量及其值可以互换使用。因此,变量的一种用途是设置将反复使用的常量。例如,很容易在变量 PI 和实数 3.14159 之间建立等效关系。
var PI = 3.14159; var radius = 10; var area = PI * radius * radius; var circumference = 2 * PI * radius;
注意,用于计算变量 area 和 circumference 值的表达式比使用 3.14159 代替 PI 更易读。事实上,这是变量的主要用途之一,使代码更易读。第二种情况是,如果 PI 的值应该改变(例如,需要一个更精确的 PI 值[5],我们只需要更改 PI 的定义(当然,这假设我们可以存储这些定义以供以后检索,并且不需要再次将它们输入解释器中)。
变量及其值存储在一个称为环境的结构中。你可以把环境想象成一个装满信封的鞋盒。当我们定义一个变量时,我们会在盒子的最前面放一个新的信封,信封的外面印着变量的名称。每个信封里面都是变量的初始化值。当我们需要一个变量的值时,我们会从鞋盒的前部到后部开始查找,寻找正确的信封。当我们找到正确标记的信封时,我们检索信封里面的值。
据说环境保存着变量及其绑定(即信封中的值)。如果变量在该环境中具有绑定,则称该变量相对于该环境绑定。当被要求评估一个变量时,Sway 只是在环境中查找该变量并检索其值。有时,绑定到变量的值是另一个环境。事实上,有一个名为this的变量,它保存着当前环境的位置(this变量是预定义的,因此你不需要自己定义它)。如果你希望查看生效的绑定,你可以通过发出以下命令询问解释器
pp(this);
如果我们在定义了上述所有变量后这样做,我们将从解释器那里得到以下响应
sway> pp(this); <OBJECT 1651>: context: <OBJECT 323> dynamicContext: null this: <OBJECT 1651> constructor: null dots: 42 bones: 206 CLXIV: 164 PI: 3.141590 radius: 10 area: 314.159000 circumference: 62.831800 OBJECT: <OBJECT 1651>
在编程语言术语中,发出pp命令是“调用函数”和“传递参数”的示例。我们将在后面学习更多关于函数的知识,但主要思想是函数执行一些有用的任务或任务集。在本例中,该任务是显示当前的变量-值对集。
除了我们之前定义的变量的绑定之外,我们还看到了一些针对constructor、dynamicContext、context和this的额外绑定。这些变量由 Sway 为每个环境预定义。在本例中,变量context绑定到定义了 Sway 内置函数的环境。正如你将在后面看到的,有时我们会尝试在当前环境中查找一个变量。如果我们没有找到它,我们会查找context变量的值,然后使用该环境继续我们的搜索。我们将在后面讨论其他预定义变量。
如果你要要求解释器显示context的绑定,你会得到一个很长的列表
sway> pp(context); <OBJECT 137>: array?: <function array?(a)> string?: <function string?(a)> real?: <function real?(a)> integer?: <function integer?(a)> ... +: <function +(a,b)> -: <function -(a,b)> *: <function *(a,b)> /: <function /(a,b)> ... if: <function if(a,$b,$c)> while: <function while($a,$b)> ... ||: <function ||> &&: <function &&> ... catch: <function catch($a)> =: <function =($a,b)> .: <function .(a,$b)> ... OBJECT: <OBJECT 137>
注意,即使变量context和this绑定到环境,pp函数也说它们绑定到对象。在 Sway 中,与其他语言不同,对象和环境是同一个东西。事实上,术语“对象”和“环境”应该都被认为是等价的。你将在后面的章节中学习更多关于对象的知识。
在显示绑定之后,解释器会显示评估pp命令的结果,该结果始终是传递给pp函数的内容。除了返回值之外还显示了一些内容,这被称为副作用。副作用不是表达式值的一部分。正是这些副作用使得依赖操作数评估顺序变得很危险。
能够查看当前绑定集是一个有用的工具,可以帮助你找出程序为什么没有按预期运行。解决此类问题的过程称为调试。
与许多语言不同,Sway 对合法变量名称和变量可以绑定的实体非常宽松。考虑以下变量声明
sway> var times = *; BUILT-IN: <function *> sway> 6 times 7; INTEGER: 42 sway> var dots+bones = 5; INTEGER: 5 sway> dots+bones; INTEGER: 5 sway> dots + bones; INTEGER: 248
第一个声明定义了一个名为times的变量,并赋予它与运算符*相同的值。请注意,解释器的响应告诉我们 times 已绑定到最初绑定到 *[6]的函数。此时,变量times和*都绑定到同一个函数,并且可以互换使用,如下一个交互所示。从这个例子中,我们可以看到运算符是普通的变量,它们恰好绑定到适当的内置函数。如前所述,这允许 Sway 程序员在创建新的运算符时拥有很大的自由度。从接下来的交互中,我们可以看到空格在区分变量名方面非常重要:dots+bones是一个单独的变量名,而dots + bones是两个名为dots和bones的独立变量的组合;具体来说,它们与绑定到变量 + 的内置函数相结合。
变量是编程语言的下一层,建立在原始表达式和表达式组合(本身也是表达式)的基础上。实际上,变量可以看作是原语的抽象。作为类比,考虑一下你的名字。你的名字不是你,但它是一种方便的(抽象的)指代你的方式。同理,变量可以看作是事物的名称。它们不是事物本身,而是一种方便的指代事物的方式。
虽然 Sway 允许你以各种方式命名变量,但你应该控制你的创造力,不要过度。例如,我们可以使用 slope 而不是变量 m 来表示斜率。
var slope = 6;
我们也可以使用不同的名称。
var !@#$% = 6;
从 Sway 的角度来看,!@#$%是一个非常好的变量名。但从使你的 Sway 程序可读的角度来看,这是一个非常糟糕的名字。你的变量名必须反映它们的目的。在上面的例子中,哪一个名字更好:m, slope, 还是 !@#$%?
脚注
[edit | edit source]- ↑ 不允许更改变量的语言称为函数式语言。Sway 是一种“不纯”的函数式语言,因为它主要功能化,但允许变量修改。
- ↑ 另一个基本概念是类比,如果你在阅读完本节后理解了信封故事的目的,那么你已经踏上了成为计算机科学家的道路!
- ↑ 计算机科学中的第三个重要基本概念是泛化。特别是,计算机科学家总是试图使事物更加抽象和通用(但不要过分)。原因是,表现出适当抽象和泛化级别的软件/系统/模型更容易理解和修改。当你需要对软件/系统/模型进行最后一刻的更改时,这尤其有用。
- ↑ 关键字是不能用作变量名的标记。与大多数语言相比,Sway 的关键字出奇地少。事实上,只有三个:var、function和else。其余关键字将在后面的章节中讨论。
- ↑ 对 PI 值的认识在几个世纪以来一直在变化,而且并不总是为了更准确(参见 [[w:History of Pi|]])
- ↑ 在 Sway 中,所有运算符都是函数。反之,所有接受两个参数的函数都是运算符。稍后将详细介绍。