|
另請參閱下列語言擴充功能:first-class modules、open 語句中的覆寫、Bigarray 存取語法、屬性、擴充節點和擴充索引運算子。
下表顯示運算子和非封閉結構的相對優先順序和結合性。優先順序較高的結構會先出現。對於中綴和前綴符號,我們寫「*…」表示「任何以 * 開頭的符號」。
結構或運算子 | 結合性 |
前綴符號 | – |
. .( .[ .{ (請參閱第 12.11 節) | – |
#… | 左結合 |
函式應用、建構子應用、標籤應用、assert、lazy | 左結合 |
- -. (前綴) | – |
**… lsl lsr asr | 右結合 |
*… /… %… mod land lor lxor | 左結合 |
+… -… | 左結合 |
:: | 右結合 |
@… ^… | 右結合 |
=… <… >… |… &… $… != | 左結合 |
& && | 右結合 |
or || | 右結合 |
, | – |
<- := | 右結合 |
if | – |
; | 右結合 |
let match fun function try | – |
測試或刷新對此的理解非常簡單
由常數組成的表達式會求值為該常數。例如,3.14 或 [||]。
由存取路徑組成的表達式會求值為目前評估環境中繫結到此路徑的值。路徑可以是值名稱,或是模組的值元件的存取路徑。
表達式 ( expr ) 和 begin expr end 的值與 expr 相同。這兩個結構在語意上等效,但最好在控制結構內使用 begin … end
if … then begin … ; … end else begin … ; … end
以及在其他群組情況下使用 ( … )。
括號括住的表達式可以包含類型約束,如 ( expr : typexpr )。此約束會強制 expr 的類型與 typexpr 相容。
括號括住的表達式也可以包含強制轉換 ( expr [: typexpr] :> typexpr) (請參閱下方的第 11.7.7 節)。
函式應用以(可能帶標籤的)表達式的並列表示。expr argument1 … argumentn 表達式會評估 expr 表達式和出現在 argument1 到 argumentn 的表達式。expr 表達式必須求值為函式值 f,然後將其套用至 argument1、…、argumentn 的值。
評估 expr、argument1、…、argumentn 表達式的順序未指定。
引數和參數會根據它們各自的標籤進行比對。引數順序不重要,除非在具有相同標籤或沒有標籤的引數之間。
如果參數在 expr 的類型中指定為選用 (標籤帶有 ? 前綴),則對應的引數會自動以建構子 Some 包裝,除非引數本身也帶有 ? 前綴,在這種情況下,會按原樣傳遞。
如果傳遞了未標記的引數,且其對應的參數前面有一個或多個選用參數,則這些參數會預設,也就是說,會為它們傳遞值 None。所有其他遺失的參數(沒有對應的引數),包括選用和非選用參數,都會保留,並且函式的結果仍將是這些遺失參數到 f 主體的函式。
在所有情況下,除了順序和標籤完全比對且沒有選用參數之外,函式類型都應該在應用點上已知。這可以透過新增類型約束來確保。可以在 -principal 模式中檢查推導的主要性。
作為特例,OCaml 支援 labels-omitted 完全應用:如果函式具有已知的元數,所有引數都未標記,且其數目與非選用參數的數目相符,則會忽略標籤,並依其定義順序比對非選用參數。選用引數會預設。不建議省略標籤,這會導致警告,請參閱 13.5.1。
提供兩種語法形式來定義函式。第一種形式由關鍵字 function 引入:
|
此表達式會求值為一個帶有一個參數的函數值。當此函數應用於值 v 時,此值會與每個模式 模式1 到 模式n 進行匹配。如果其中一個匹配成功,也就是說,如果值 v 與某個 i 的模式 模式i 匹配,則會對與所選模式關聯的表達式 expri 進行求值,而其值會成為函數應用程式的值。expri 的求值發生在匹配期間執行的綁定所豐富的環境中。
如果多個模式與參數 v 匹配,則會選取函數定義中首先出現的模式。如果沒有任何模式與參數匹配,則會引發異常 Match_failure。
此表達式等同於
可以在 -> 之前新增選用的類型約束 類型表達式,以強制結果的類型與約束 類型表達式 相容
等同於
請注意最後一個參數的類型約束之間語法上的細微差異
以及結果的類型約束
參數模式 ~標籤名稱 和 ~(標籤名稱 [: 類型]) 分別是 ~標籤名稱:標籤名稱 和 ~標籤名稱:(標籤名稱 [: 類型]) 的簡寫形式,選用的對應形式也類似。
形式為 fun ? 標籤名稱 :( 模式 = expr0 ) -> expr 的函數等同於
其中 識別碼 是一個新的變數,除非未指定何時求值 expr0。
經過這兩個轉換後,表達式的形式為
如果我們忽略標籤(標籤僅在函數應用時才有意義),則此表達式等同於
也就是說,上面的 fun 表達式會求值為一個具有 n 個參數的柯里化函數:將此函數應用 n 次於值 v1 … vn 後,這些值將會平行地與模式 pattern1 … patternn 進行匹配。如果匹配成功,函數將會回傳 expr 的值,並使用匹配期間建立的綁定來擴充環境。如果匹配失敗,則會拋出 Match_failure 例外。
模式匹配的 case (在 function、match 和 try 建構子中) 可以包含守衛表達式,這些是任意的布林表達式,必須求值為 true 才能選中該匹配 case。守衛出現在 -> 符號之前,並由 when 關鍵字引入
|
匹配的進行方式與之前描述的相同,但如果值匹配到某個具有守衛 condi 的模式 patterni,則會對表達式 condi 進行求值 (在匹配期間建立的綁定所擴充的環境中)。如果 condi 求值為 true,則會對 expri 進行求值,並照常回傳其值作為匹配的結果。但如果 condi 求值為 false,則會針對 patterni 後面的模式繼續進行匹配。
let 和 let rec 建構子會將值名稱局部綁定。建構子
會以未指定的順序求值 expr1 … exprn,並將它們的值與模式 pattern1 … patternn 進行匹配。如果匹配成功,則會在匹配期間建立的綁定所擴充的環境中求值 expr,並回傳 expr 的值作為整個 let 表達式的值。如果其中一個匹配失敗,則會拋出 Match_failure 例外。
提供了一個替代語法來將變數綁定到函數值:與其寫
在 let 表達式中,可以改寫成
使用 let rec 引入名稱的遞迴定義
與上面描述的 let 建構子唯一不同的是,當求值表達式 expr1 到 exprn 時,會將模式匹配執行的名稱與值的綁定視為已經執行。也就是說,表達式 expr1 到 exprn 可以引用由其中一個模式 pattern1, …, patternn 綁定的識別符號,並期望它們的值與 expr ( let rec 建構子的主體) 中的值相同。
如果表達式 expr1 到 exprn 是函數定義 (fun … 或 function …),且模式 pattern1 … patternn 只是值名稱,如以下範例,則保證遞迴定義的行為如上所述
這將 name1 … namen 定義為 expr 的區域互遞迴函數。
其他形式的 let rec 定義的行為與實作相關。目前的實作也支援某種類型的非函數值遞迴定義,如第 12.1 節所述。
(於 OCaml 4.04 中引入)
可以在表達式中定義區域例外:let exception constr-decl in expr 。
例外建構子的語法作用域是內部表達式,但沒有任何規定阻止使用此建構子建立的例外值逸出此作用域。上面定義的兩個執行結果會導致兩個不相容的例外建構子(如同任何例外定義)。例如
(於 OCaml 3.12 中引入)
let 定義中的多型類型註解的行為方式與多型方法相似
這些註釋明確要求定義的值為多型(polymorphic),並允許在遞迴呼叫中使用此多型(當使用 let rec 時)。但請注意,這是一個正常的多型型別,可以與自身的任何實例統一。
表達式 expr1 ; expr2 會先計算 expr1,然後計算 expr2,並傳回 expr2 的值。
表達式 if expr1 then expr2 else expr3,如果 expr1 計算結果為布林值 true,則計算結果為 expr2 的值;如果 expr1 計算結果為布林值 false,則計算結果為 expr3 的值。
可以省略 else expr3 部分,在這種情況下,它預設為 else ()。
表達式
|
會將 expr 的值與模式 pattern1 到 patternn 進行匹配。如果與 patterni 的匹配成功,則會計算相關的表達式 expri,並且其值會成為整個 match 表達式的值。expri 的計算發生在匹配期間執行的綁定所豐富的環境中。如果多個模式與 expr 的值匹配,則選擇在 match 表達式中首先出現的模式。
如果沒有任何模式與 expr 的值匹配,則會引發例外 Match_failure。
表達式 expr1 && expr2,如果 expr1 和 expr2 的計算結果都為 true,則計算結果為 true;否則,計算結果為 false。首先會計算第一個組成部分 expr1。如果第一個組成部分的計算結果為 false,則不會計算第二個組成部分 expr2。因此,表達式 expr1 && expr2 的行為與
表達式 expr1 || expr2,如果表達式 expr1 和 expr2 其中一個的計算結果為 true,則計算結果為 true;否則,計算結果為 false。首先會計算第一個組成部分 expr1。如果第一個組成部分的計算結果為 true,則不會計算第二個組成部分 expr2。因此,表達式 expr1 || expr2 的行為與
布林運算子 & 和 or 是 && 和 || 的已棄用的同義詞(分別)。
表達式 while expr1 do expr2 done 會重複計算 expr2,直到 expr1 的計算結果為 true。迴圈條件 expr1 會在每次迭代開始時計算和測試。整個 while … done 表達式的計算結果為單位值 ()。
作為一個特殊情況,while true do expr done 被賦予一個多型型別,允許它用於取代任何表達式(例如,作為任何模式匹配的分支)。
表達式 for name = expr1 to expr2 do expr3 done 會先計算表達式 expr1 和 expr2 (邊界值),將其轉換為整數值 n 和 p。接著,迴圈主體 expr3 會在 name 依序綁定到 n, n+1, …, p−1, p 這些值的環境中重複計算。如果 n > p,迴圈主體將不會被計算。
表達式 for name = expr1 downto expr2 do expr3 done 的計算方式類似,差別在於 name 依序綁定到 n, n−1, …, p+1, p 這些值。如果 n < p,迴圈主體將不會被計算。
在這兩種情況下,整個 for 表達式的計算結果都是單位值 ()。
表達式
|
會計算表達式 expr,如果計算 expr 的過程中沒有引發任何例外,則回傳其值。如果計算 expr 的過程中引發了例外,則會將例外值與模式 pattern1 到 patternn 進行匹配。如果與 patterni 的匹配成功,則會計算相關的表達式 expri,並且其值會成為整個 try 表達式的值。計算 expri 的過程會在匹配過程中建立的綁定所擴充的環境中進行。如果有多個模式匹配 expr 的值,則會選擇 try 表達式中第一個出現的匹配模式。如果沒有任何模式匹配 expr 的值,則例外值會再次被引發,從而透明地「穿透」 try 結構。
表達式 expr1 , … , exprn 的計算結果是表達式 expr1 到 exprn 值的 n 元組。子表達式的計算順序未指定。
表達式 constr expr 的計算結果為一元變體值,其建構子為 constr,而參數為 expr 的值。同樣地,表達式 constr ( expr1 , … , exprn ) 的計算結果為 n 元變體值,其建構子為 constr,而參數為 expr1, …, exprn 的值。
表達式 constr (expr1, …, exprn) 的計算結果為變體值,其建構子為 constr,而參數為 expr1 … exprn 的值。
對於列表,提供了一些語法糖。表達式 expr1 :: expr2 代表將建構子 ( :: ) 應用於參數 ( expr1 , expr2 ),因此計算結果為列表,其頭部是 expr1 的值,而尾部是 expr2 的值。表達式 [ expr1 ; … ; exprn ] 等同於 expr1 :: … :: exprn :: [],因此計算結果為列表,其元素為 expr1 到 exprn 的值。
表達式 `tag-name expr 的計算結果為多型變體值,其標籤為 tag-name,而參數為 expr 的值。
表達式 { field1 [= expr1] ; … ; fieldn [= exprn ]} 的求值結果為記錄值 { field1 = v1; …; fieldn = vn },其中 vi 是 expri 的值,對於 i = 1,… , n。單一識別符 fieldk 代表 fieldk = fieldk,而限定識別符 module-path . fieldk 代表 module-path . fieldk = fieldk。欄位 field1 到 fieldn 必須都屬於同一個記錄類型;此記錄類型的每個欄位都必須在記錄表達式中恰好出現一次,儘管它們可以以任何順序出現。expr1 到 exprn 的求值順序未指定。可以在每個欄位後新增可選的類型約束 { field1 : typexpr1 = expr1 ;… ; fieldn : typexprn = exprn },以強制 fieldk 的類型與 typexprk 相容。
表達式 { expr with field1 [= expr1] ; … ; fieldn [= exprn] } 會建立一個新的記錄,其欄位 field1 … fieldn 等於 expr1 … exprn,而所有其他欄位的值則與記錄 expr 中的值相同。換句話說,它會傳回記錄 expr 的淺層副本,除了欄位 field1 … fieldn 之外,這些欄位會初始化為 expr1 … exprn。如同先前一樣,單一識別符 fieldk 代表 fieldk = fieldk,限定識別符 module-path . fieldk 代表 module-path . fieldk = fieldk,並且可以針對每個正在更新的欄位新增可選的類型約束,寫法為 { expr with field1 : typexpr1 = expr1 ; … ; fieldn : typexprn = exprn }。
表達式 expr1 . field 會將 expr1 求值為記錄值,並傳回此記錄值中與 field 相關聯的值。
表達式 expr1 . field <- expr2 會將 expr1 求值為記錄值,然後藉由將此記錄中與 field 相關聯的值取代為 expr2 的值,對該記錄進行原地修改。只有在記錄類型的定義中已將 field 宣告為 mutable 時,才允許此操作。整個表達式 expr1 . field <- expr2 的求值結果為單位值 ()。
表達式 [| expr1 ; … ; exprn |] 的求值結果為一個 n 個元素的陣列,其元素分別使用 expr1 到 exprn 的值初始化。這些表達式的求值順序未指定。
表達式 expr1 .( expr2 ) 會傳回以 expr1 表示的陣列中,編號為 expr2 的元素的值。第一個元素的編號為 0;最後一個元素的編號為 n−1,其中 n 為陣列的大小。如果存取超出界限,則會引發 Invalid_argument 例外狀況。
表達式 expr1 .( expr2 ) <- expr3 會原地修改以 expr1 表示的陣列,將編號為 expr2 的元素取代為 expr3 的值。如果存取超出界限,則會引發 Invalid_argument 例外狀況。整個表達式的值為 ()。
表達式 expr1 .[ expr2 ] 會返回由 expr1 所表示的字串中,位於字元編號 expr2 的字元值。第一個字元的編號為 0;最後一個字元的編號為 n−1,其中 n 是字串的長度。如果存取超出邊界,會引發 Invalid_argument 例外。
表達式 expr1 .[ expr2 ] <- expr3 會就地修改由 expr1 所表示的字串,將字元編號 expr2 的字元替換為 expr3 的值。如果存取超出邊界,會引發 Invalid_argument 例外。整個表達式的值為 ()。注意:此功能僅為與舊版 OCaml 的向後相容性而提供,並將在未來版本中移除。新的程式碼應使用位元組序列和 Bytes.set 函式。
來自 infix-symbol 類別的符號,以及關鍵字 *、+、-、-.、=、!=、<、>、or、||、&、&&、:=、mod、land、lor、lxor、lsl、lsr 和 asr 可以出現在中綴位置(兩個表達式之間)。來自 prefix-symbol 類別的符號,以及關鍵字 - 和 -. 可以出現在前綴位置(在表達式之前)。
中綴和前綴符號沒有固定的意義:它們只是被解釋為繫結到與符號對應名稱的函式的應用。表達式 prefix-symbol expr 會被解釋為應用 ( prefix-symbol ) expr。類似地,表達式 expr1 infix-symbol expr2 會被解釋為應用 ( infix-symbol ) expr1 expr2。
下表列出了初始環境中定義的符號及其初始含義。(有關詳細資訊,請參閱第 28 章中核心程式庫模組 Stdlib 的說明)。它們的含義可以隨時使用 let ( infix-op ) name1 name2 = … 來變更。
注意:運算子 &&、|| 和 ~- 會被特殊處理,不建議變更它們的含義。
關鍵字 - 和 -. 可以同時作為中綴和前綴運算子出現。當它們作為前綴運算子出現時,它們分別被解釋為函式 (~-) 和 (~-.)。
運算子 | 初始含義 |
+ | 整數加法。 |
-(中綴) | 整數減法。 |
~- -(前綴) | 整數取負。 |
* | 整數乘法。 |
/ | 整數除法。如果第二個引數為零,則引發 Division_by_zero 例外。 |
mod | 整數模數。如果第二個引數為零,則引發 Division_by_zero 例外。 |
land | 整數的按位邏輯「且」。 |
lor | 整數的按位邏輯「或」。 |
lxor | 整數的按位邏輯「互斥或」。 |
lsl | 整數的按位邏輯左移。 |
lsr | 整數的按位邏輯右移。 |
asr | 整數的按位算術右移。 |
+. | 浮點數加法。 |
-.(中綴) | 浮點數減法。 |
~-. -.(前綴) | 浮點數取負。 |
*. | 浮點數乘法。 |
/. | 浮點數除法。 |
** | 浮點數指數運算。 |
@ | 串列串接。 |
^ | 字串串接。 |
! | 取值(傳回參考的目前內容)。 |
:= | 參考指定(使用第二個引數的值更新作為第一個引數給出的參考)。 |
= | 結構相等測試。 |
<> | 結構不等測試。 |
== | 實體相等測試。 |
!= | 實體不等測試。 |
< | 「小於」測試。 |
<= | 「小於或等於」測試。 |
> | 「大於」測試。 |
>= | 「大於或等於」測試。 |
&& & | 布林值合取。 |
|| 或 | 布林值析取。 |
當 class-path 計算為類別主體時,new class-path 會計算為一個新的物件,其中包含此類別的實例變數和方法。
當 class-path 計算為類別函式時,new class-path 會計算為一個函式,該函式會預期相同數量的引數,並傳回此類別的新物件。
透過 object class-body end 建構直接建立物件,在操作上等效於在本機定義一個 class class-name = object class-body end —關於 class-body 的語法,請參閱 11.9.2 節及後續章節—並立即透過 new class-name 從其中建立單一物件。
即時物件的型別與明確定義類別的方式略有不同,有兩個方面。首先,推斷出的物件型別可能包含自由型別變數。其次,由於即時物件的類別主體永遠不會被擴展,因此其自我型別可以與封閉物件型別統一。
表達式 expr # method-name 會調用由 expr 所表示的物件的 method-name 方法。
如果 method-name 是一個多型方法,則其型別應在調用位置已知。如果 expr 是一個新物件的名稱 (let ident = new class-path … ) 或存在型別約束時,情況就是如此。可以在 -principal 模式中檢查推導的主要性。
類別的實例變數僅在同一個類別中定義的方法主體內,或繼承自定義實例變數的類別中可見。表達式 inst-var-name 會求值為給定實例變數的值。表達式 inst-var-name <- expr 會將 expr 的值賦給實例變數 inst-var-name,而該變數必須是可變的。整個表達式 inst-var-name <- expr 的求值結果為 ()。
可以使用函式庫函式 Oo.copy 來複製物件(請參閱模組 Oo)。在方法內部,表達式 {< [inst-var-name [= expr] { ; inst-var-name [= expr] }] >} 會返回自身的副本,並將給定的實例變數替換為相關表達式的值。單個實例變數名稱 id 代表 id = id。其他實例變數在返回的物件中與自身具有相同的值。
類型包含物件或多型變體類型的表達式可以顯式強制轉換(弱化)為超類型。表達式 (expr :> typexpr) 會將表達式 expr 強制轉換為類型 typexpr。表達式 (expr : typexpr1 :> typexpr2) 會將表達式 expr 從類型 typexpr1 強制轉換為類型 typexpr2。
即使類型 typ1 是類型 typ2 的子類型,前一種運算符有時也會無法將表達式 expr 從類型 typ1 強制轉換為類型 typ2:在目前的實作中,它僅會展開包含物件和/或多型變體的兩個層級的類型縮寫,僅在類別類型中明確時才會保留遞迴。作為上述演算法的例外,如果 expr 的推論類型和 typ 都是基本類型(即,不包含類型變數),則前一種運算符的行為與後一種運算符相同,將 expr 的推論類型視為 typ1。如果前一種運算符失敗,則應使用後一種運算符。
只有在 expr 的類型是 typ1 的實例(如類型註釋),並且 typ1 是 typ2 的子類型時,才有可能將表達式 expr 從類型 typ1 強制轉換為類型 typ2。強制轉換的表達式的類型是 typ2 的實例。如果類型包含變數,它們可能會由子類型演算法實例化,但僅在確定 typ1 是否為 typ2 的潛在子類型之後才會執行此操作。這表示類型化可能會在此後的統一化步驟中失敗,即使 typ1 的某些實例是 typ2 的某些實例的子類型。在以下段落中,我們將描述使用的子類型關係。
固定的物件類型會將包含所有其方法的所有物件類型視為子類型。方法的類型應為超類型中這些方法的子類型。也就是說,
是
的超類型,如果每個 typi 都是對應的 typ′i 的超類型,則該類型可能包含省略號 ..。
單態方法類型可以是多型方法類型的超類型。也就是說,如果 typ 是 typ′ 的實例,則 'a1 … 'an . typ′ 是 typ 的子類型。
在類別定義中,新定義的類型不可用於子類型化,因為類型縮寫尚未完全定義。將 self 強制轉換為其類別的(精確)類型有一個例外:如果 self 的類型未出現在類別類型中的逆變位置(即,如果沒有二元方法),則允許此操作。
如果 typ 的上限(即,可能出現在 typ 實例中的最大建構子集合)包含在 typ′ 的下限中,並且 typ 的建構子的引數類型是 typ′ 中的子類型,則多型變體類型 typ 是另一個多型變體類型 typ′ 的子類型。也就是說,
可能是可縮減的類型,是
如果每個 typi 都是 typ′i 的子型別,則這可能是一個可擴展的型別。
其他型別不會引入新的子型別關係,但它們可能會傳播其參數的子型別關係。例如,當 typ1 和 typ2 分別是 typ′1 和 typ′2 的子型別時,typ1 * typ2 是 typ′1 * typ′2 的子型別。對於函數型別,關係更微妙:如果 typ1 是 typ′1 的超型別,且 typ2 是 typ′2 的子型別,則 typ1 -> typ2 是 typ′1 -> typ′2 的子型別。因此,函數型別在其第二個參數中是協變的(如同元組),但在其第一個參數中是逆變的。如同 array 或 ref 等可變型別既不是協變的也不是逆變的,它們是不變的,也就是說它們不傳播子型別關係。
對於使用者定義的型別,變異性會自動推斷:如果一個參數只有協變的出現,則它是協變的;如果它只有逆變的出現,則它是逆變的;如果它沒有出現,則它是無變異性的;否則它是非變異的。無變異性的參數可以在子型別關係中自由變更,它不必是子型別或超型別。對於抽象和私有型別,必須明確給出變異性(請參閱 11.8.1 節),否則預設為非變異性。對於型別定義中的受限參數也是如此。
OCaml 支援 assert 建構來檢查偵錯斷言。表達式 assert expr 會評估表達式 expr,如果 expr 的評估結果為 true,則會傳回 ()。如果評估結果為 false,則會引發例外 Assert_failure,並以原始檔案名稱和 expr 的位置作為引數。可以使用 -noassert 編譯器選項來關閉斷言檢查。在這種情況下,根本不會評估 expr。
在特殊情況下,assert false 會簡化為 raise (Assert_failure ...),這會使其具有多型別。這表示它可以被用來替代任何表達式(例如作為任何模式比對的分支)。這也表示 assert false「斷言」無法透過 -noassert 選項關閉。
表達式 lazy expr 會傳回類型為 Lazy.t 的值 v,該值封裝了 expr 的計算。在這個程式碼點,不會評估引數 expr。相反地,其評估將在第一次將函數 Lazy.force 應用於值 v 時執行,並傳回 expr 的實際值。後續將 Lazy.force 應用於 v 不會再次評估 expr。可以透過模式比對隱式地應用 Lazy.force(請參閱 11.6)。
表達式 let module module-name = module-expr in expr 會在評估表達式 expr 期間,將模組表達式 module-expr 局部繫結至識別符號 module-name。然後,它會傳回 expr 的值。例如:
表達式 let open module-path in expr 和 module-path.(expr) 是完全等效的。這些結構會在表達式 expr 的各自範圍內局部開啟由模組路徑 module-path 所參照的模組。
當局部開啟表達式的主體以 [ ]、[| |] 或 { } 分隔時,可以省略括號。對於表達式,也可以省略 {< >} 的括號。例如,module-path.[expr] 等同於 module-path.([expr]),而 module-path.[| expr |] 等同於 module-path.([| expr |])。