塊 (編程)

在電腦編程中，塊（block）或譯為程式區塊、代碼塊，是將原始碼組織在一起的詞法結構。塊構成自一個或多個聲明和語句。程式語言允許創建塊，包括嵌入其他塊之內的塊，就叫做塊結構程式語言。塊和子程序是結構化編程的基礎，結構化所強調的控制結構可以用塊來形成的。

在編程中塊的功能，是確使成組的語句被當作如同就是一個語句，限定在一個塊中聲明的對象如變量、過程和函數的詞法作用域，使得它們不衝突於在其他地方用到的同名者。在塊結構程式語言中，在塊外部的對象名字在塊內部是可見的，除非它們被聲明了相同名字的對象所屏蔽。

## 歷史

塊結構的想法是在1950年代開發最初的Autocode期間發展出來的，並形式化於ALGOL 60報告中。ALGOL 58介入了「複合」（compound）語句的概念，它只與控制流程有關。在「ALGOL 60報告」中，介入了塊和作用域的概念。最終在「修訂報告」中，複合語句被定義為：包圍在語句括號`begin`和`end`之間的成序列的語句，形成一個複合語句。塊被定義為：成序列的聲明，跟隨著成序列的語句，並被包圍在`begin`和`end`之間，形成一個塊；所有聲明以這種方式出現在一個塊中，並只在這個塊中有效。塊與複合語句的主要差異是不能從塊外跳轉到塊內的標籤。

## 語法

塊在不同語言家族中使用不同的語法：

- ALGOL語言家族，ALGOL 60及其後繼者比如Simula，使用語句括號`begin`和`end`來界定複合語句和塊。
- ALGOL 68成為了面向表達式程式語言，偏好使用與`begin`和`end`等價的圓括號`(`和`)`。
- Pascal語言家族，在Pascal中塊就是程序、過程和函數的主體，它細分為六節，前五節中任何一個或多個都可以空無，在最後的語句部份使用關鍵字`begin`和`end`來界定複合語句。
- C語言家族，C承襲了BCPL，使用花括號`{`和`}`來界定複合語句或塊。
- Lisp語言家族，Lisp 1.5使用具有語法關鍵字`prog`的S-表達式表示塊，而Maclisp和Scheme使用`let`形式的S-表達式來表示塊，S-表達式是圓括號`(`和`)`包圍的前綴表示法。
- Smalltalk語言家族，Smalltalk-80和Self使用方括號`[`和`]`來界定塊。

此外，複合語句界定還可以採用：

- 越位規則，源於ISWIM使用縮進表示塊結構，採用越位規則的語言有occam、Python和Nim等。

建立控制結構，除了將所控制的語句序列，包圍入複合語句或匿名塊之外，還可以採用其他語法機制：

- 在ALGOL 68中，條件和迭代語句，使用塊首保留字的反寫保留字來終止，比如：`<b>IF</b> ~ THEN ~ ELIF ~ THEN ~ ELSE ~ <b>FI</b>`和`FOR ~ FROM ~ TO ~ BY ~ WHILE ~ <b>DO</b> ~ <b>OD</b>`。繼承此風格的有：Dijkstra的守衛命令語言和Bourne的Bourne shell等。
- 一些結構化程式語言，如FORTRAN 77、Modula-2、Ada和Visual Basic等，對控制結構加結束關鍵字，比如Modula-2中的：`IF … THEN … ELSIF … THEN … ELSE … END`和`FOR … TO … BY … DO … END`。

## 限制

受ALGOL影響的一些語言支持塊，但有著各自的限制：

- C家族語言，在塊和複合語句之中不僅支持嵌套入複合語句，還支持嵌入帶有聲明的匿名塊，但不允許聲明嵌套函數。
- Pascal家族語言，在語句部份的複合語句之中，不允許存在帶有聲明的匿名塊，只支持複合語句，用來在`if`、`while`、`repeat`等控制語句內組合語句序列。

## 基本語義

塊的語義是雙重的。首先，它向編程者提供了建立任意大和複雜的結構，並把它當作一個單元的一種途徑。其次，它確使編程者能限制變量的作用域，有時可以限制已經被聲明了的其他對象的作用域。

在早期語言比如FORTRAN和BASIC中，沒有語句塊或控制結構。直到1978年標準化FORTRAN 77之前，都沒有「塊狀`IF`」語句，要實現按條件選擇，必須訴諸`GOTO`語句。例如下述FORTRAN代碼片段，從雇員工資中分別扣除超出正稅閾值部分的稅款，和超出附加稅閾值部分的附加稅款：

```fortranfixed
C     语言：ANSI标准FORTRAN 66
C     初始化要计算的值
      PAYSTX = .FALSE.
      PAYSST = .FALSE.
      TAX = 0.0
      SUPTAX = 0.0
C     如果雇员挣钱小于等于正税阈值则跃过税款扣除
      IF (WAGES .LE. TAXTHR) GOTO 10
        PAYSTX = .TRUE.
        TAX = (WAGES - TAXTHR) * BASCRT
   10 CONTINUE
C     如果雇员挣钱小于等于附加税阈值则跃过附加税扣除
      IF (WAGES .LE. SUPTHR) GOTO 20
        PAYSST = .TRUE.
        SUPTAX = (WAGES - SUPTHR) * SUPRAT
   20 CONTINUE
      TAXED = WAGES - TAX - SUPTAX
```

程序的邏輯結構不反映在代碼中，這裡的初始化的值，是後面的有關邏輯判斷為假時所應當設置的值。

塊允許編程者把一組語句當作一個單元。例如，在與上述FORTRAN代碼相對應的Pascal代碼片段：

```pascal
{ 语言：Jensen与Wirth版标准Pascal }
if Wages > TaxThreshold then
begin
    PaysTax := true;
    Tax := (Wages - TaxThreshold) * TaxRate
end
else begin
    PaysTax := false;
    Tax := 0
end;
if Wages > SupertaxThreshold then
begin
    PaysSupertax := true;
    Supertax := (Wages - SupertaxThreshold) * SupertaxRate
end
else begin
    PaysSupertax := false;
    Supertax := 0
end;
Taxed := Wages - Tax - Supertax;
```

與上述FORTRAN代碼相比，上例中出現在初始化中的那些預設值，通過複合語句即不帶聲明的塊結構，被分別放置作出有關邏輯判斷的地方。使用塊結構，能明晰編程者的意圖，使代碼的結構更加密切反映出編程者的思考；再憑藉某種風格的縮進和駝峰式大小寫增進可讀性，可使代碼更加容易理解和修改。

在早期語言中，在子例程中變量的作用域遍及整個子例程。假想在一個Fortran子例程中，完成了與管理者有關的任務，這裡可能用到叫做`IEMPNO`的一個整數變量，指示作為管理者的雇員的社會安全號碼（SSN）；後來在這個子例程的維護工作中，又增加與下屬們有關的任務，此時編程者可能不經意間使用同名變量`IEMPNO`，指示了作為這個管理者的下屬的雇員的SSN，這就會導致一個難於跟蹤的缺陷。

塊結構使得編程者能夠容易地將作用域控制到細微級別。例如完成有關雇員任務的Scheme代碼片段：

```scheme
;; 语言：R5RS标准Scheme
(let ((empno (ssn-of employee-name)))
  (when (is-manager? empno) ;; when已列入R7RS-small标准
    (let ((employee-list (underlings-of empno)))
      (display
        ;; format是SRFI-28和SRFI-48规定的字符串格式化过程
        (format "~a has ~a employees working under him:~%"
          employee-name (length employee-list)))
      (for-each
        (lambda (empno)
          (display
            (format "Name: ~a, role: ~a~%"
              (name-of empno) (role-of empno))))
        employee-list))))
```

這裡在外層通過綁定宏`let`將管理者的SSN綁定到了局部變量`empno`，在其形成的塊的作用域中列出管理者的雇員名字和他的下屬數目；隨後通過`for-each`高階函數，將他所有下屬的SSN逐個綁定到匿名函數`lambda`的形式參數`empno`上，執行此匿名函數列出這個下屬的名字和角色；這個形式參數的作用域是此匿名函數的主體，它與其外層的局部變量，標識符重名但不相互影響。在實踐中，出於清晰性的考慮，編程者更可能選取明顯不同的變量名字，但是即使名字選取存在重複，也難以在不經意間介入一個缺陷。在基於S-表達式的語言中，經常見到大量的嵌套圓括號，故而其代碼必須採用良好的縮進。

## 提升

在一些語言中，變量可以聲明為有函數作用域即使它位於函數的內嵌塊之中。例如在JavaScript中，變量應當總是在使用之前被聲明，它曾經允許賦值到未聲明變量，會為此建立為未聲明的全局變量，這在`strict`模態下是個錯誤。以`var`聲明的變量有函數作用域，而非以`let`或`const`聲明的變量可從屬的塊作用域。以`var`聲明的變量會被提升（hoist），這意味著可以在這個函數的作用域內任何地方提及這個變量，即使還未觸及到它的聲明，從而可以將`var`聲明視為被提舉（lift）到它所在函數的頂部或全局作用域。但是如果在其聲明之前訪問了一個變量，這個變量的值總是未指定的。