跳轉到內容

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 中,所有運算子都是函式。反之,所有接受兩個引數的函式都是運算子。稍後將詳細介紹。


優先順序和結合性 · 賦值

華夏公益教科書