跳转到内容

Sway 参考手册/变量和环境

来自维基教科书,开放世界中的开放书籍

假设你在街上发现一个信封,信封正面印着numberOfDog'sTeeth这个名字。假设你打开信封,里面是一张纸,上面写着数字 42。你会从这种遭遇中得出什么结论?现在假设你继续走,发现另一个标有meaningOfLifeUniverseEverything的信封,你再次打开它,发现一张写着数字 42 的纸条。在路途更远的地方,你又发现了两个信封,分别标有numberOfDotsOnPairOfDiceStatuteOfLibertyArmLength,两者都包含数字 42。

最后,你发现一个标有sixTimesNine的信封,你再次在里面发现数字 42。此时,你可能在想“有人对数字 42 有着奇怪的偏爱”,但随后你脑海中某个昏暗角落里的乘法表开始对你大喊大叫,说“54!是 54!”。经过一段令人尴尬的长时间后,你意识到 6 * 9 不是 42,而是 54。所以你划掉了最后一个信封中的 42,改写成 54,然后把信封放回原处。

这个奇怪的小故事,信不信由你,对编写人类和计算机都能理解的程序具有深远的影响。对于编程语言来说,信封是变量的隐喻,变量可以被认为是内存中某个位置的标签,原始值可以驻留在该位置。在许多编程语言中,可以更改该内存位置的值,就像更换信封的内容一样[1]。变量是我们第一次接触到一个称为抽象的概念,这个概念是整个计算机科学的基础[2]

很可能你之前遇到过“变量”这个词。考虑一条特定直线的代数方程的斜截式

你可能可以从这个方程中看出这条直线的斜率是 2,它与y轴的交点是 -3。但是字母yx实际上起什么作用呢?xy是占位符,代表这条直线上任何可想而知的点的x坐标和y坐标。如果没有占位符,这条直线将不得不通过列出直线上的每个点来描述。由于有无限多个点,显然不可能提供一个详尽的列表。正如你在代数课上学到的那样,占位符的通用名称是变量

可以将上述直线推广,得到一个描述每条直线的方程[3]

在这里,变量m代表斜率,b代表y截距。显然,这个方程不是由计算机科学家想出来的,因为一条基本规则是为变量选择好名字,例如,s代表斜率,i代表截距。但遗憾的是,由于历史原因,我们仍然使用mb

术语“变量”也在大多数编程语言(包括 Sway)中使用,并且该术语具有大致相同的含义。不同之处在于编程语言使用信封隐喻,而代数则不使用(这种差异纯粹是哲学上的,现在还没有必要讨论)。假设你发现三个信封,分别标有mxb,在这些信封中你分别找到了数字 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命令是“调用函数”和“传递参数”的示例。我们将在后面学习更多关于函数的知识,但主要思想是函数执行一些有用的任务或任务集。在本例中,该任务是显示当前的变量-值对集。

除了我们之前定义的变量的绑定之外,我们还看到了一些针对constructordynamicContextcontextthis的额外绑定。这些变量由 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>

注意,即使变量contextthis绑定到环境,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是两个名为dotsbones的独立变量的组合;具体来说,它们与绑定到变量 + 的内置函数相结合。

变量是编程语言的下一层,建立在原始表达式和表达式组合(本身也是表达式)的基础上。实际上,变量可以看作是原语的抽象。作为类比,考虑一下你的名字。你的名字不是你,但它是一种方便的(抽象的)指代你的方式。同理,变量可以看作是事物的名称。它们不是事物本身,而是一种方便的指代事物的方式。

虽然 Sway 允许你以各种方式命名变量,但你应该控制你的创造力,不要过度。例如,我们可以使用 slope 而不是变量 m 来表示斜率。

   var slope = 6;

我们也可以使用不同的名称。

   var !@#$% = 6;

从 Sway 的角度来看,!@#$%是一个非常好的变量名。但从使你的 Sway 程序可读的角度来看,这是一个非常糟糕的名字。你的变量名必须反映它们的目的。在上面的例子中,哪一个名字更好:m, slope, 还是 !@#$%?

脚注

[edit | edit source]
  1. 不允许更改变量的语言称为函数式语言。Sway 是一种“不纯”的函数式语言,因为它主要功能化,但允许变量修改。
  2. 另一个基本概念是类比,如果你在阅读完本节后理解了信封故事的目的,那么你已经踏上了成为计算机科学家的道路!
  3. 计算机科学中的第三个重要基本概念是泛化。特别是,计算机科学家总是试图使事物更加抽象和通用(但不要过分)。原因是,表现出适当抽象和泛化级别的软件/系统/模型更容易理解和修改。当你需要对软件/系统/模型进行最后一刻的更改时,这尤其有用。
  4. 关键字是不能用作变量名的标记。与大多数语言相比,Sway 的关键字出奇地少。事实上,只有三个:varfunctionelse。其余关键字将在后面的章节中讨论。
  5. 对 PI 值的认识在几个世纪以来一直在变化,而且并不总是为了更准确(参见 [[w:History of Pi|]])
  6. 在 Sway 中,所有运算符都是函数。反之,所有接受两个参数的函数都是运算符。稍后将详细介绍。


优先级和结合性 · 赋值

华夏公益教科书