程式碼格式化方針 (F#)
本主題摘要說明 F# 程式碼縮排方針。 因為使用 F# 語言必須注意分行符號和縮排,所以正確設定程式碼的格式不僅是可讀性問題、美感問題或程式碼撰寫標準化問題而已。 您必須正確設定程式碼的格式,才能讓程式碼正確編譯。
縮排的一般規則
需要縮排時,您必須使用空格而非定位點。 至少需有一個空格。 您的組織可以建立程式碼撰寫標準,指定縮排所使用的空格數目;在每個需要用到縮排的層次使用三個或四個空格縮排,是很常見的。 您可以設定 Visual Studio 以符合組織縮排標準,只要在從 [工具] 功能表存取的 [選項] 對話方塊中變更選項即可。 在 [文字編輯器] 節點中展開 [F#],然後按一下 [索引標籤]。如需可用選項的描述,請參閱索引標籤、所有語言、文字編輯器、選項。
一般而言,編譯器剖析程式碼時,會用一個內部堆疊紀錄目前的巢狀層次。 當程式碼縮排時,會建立新的巢狀層次或推入至此內部堆疊。 當建構結束時,此層次會推出。 縮排是表示層次結尾及推出內部堆疊的一種方式,但是某些語彙基元也會使層次推出,例如 end 關鍵字或者右括號或右大括號。
多行建構 (例如型別定義、函式定義)、try...with 建構和迴圈建構中的程式碼必須相對於建構起始行縮排。 縮排的第一行會確定相同建構中後續程式碼的欄位置。 縮排層次稱為「內容」(Context)。 欄位置會針對相同內容中後續幾行程式碼設定最小欄,稱為「越位行」(Offside Line)。 當一行程式碼縮排的欄位置小於此設定的欄位置時,編譯器便會認定這一段內容已結束,接下來的程式碼屬於上一個層次,也就是前一個內容。 「越位」(Offside) 一詞是指某行程式碼的縮排較短,作為標示出建構結尾的位置。 換句話說,在越位行左邊的程式碼就是越位。 在正確縮排的程式碼中,您可以利用越位規則界定建構結尾。 如果不正確地使用縮排,越位狀況可能使編譯器發出警告或導致程式碼解譯不正確。
越位行的判斷方式如下:
let 後面會有一個相關聯的 = 語彙基元,跟在此 = 符號後面的第一個語彙基元的欄位置,就是新的越位行。
在 if...then...else 運算式中,then 關鍵字和 else 關鍵字後面第一個語彙基元的欄位置,就是新的越位行。
在 try...with 運算式中,try 後面的第一個語彙基元,就是新的越位行。
在 match 運算式中,with 後面的第一個語彙基元以及每個 -> 後面的第一個語彙基元,就是新的越位行。
在型別擴充中,with 後面的第一個語彙基元就是新的越位行。
左括號或左大括號後面的第一個語彙基元,以及 begin 關鍵字後面的第一個語彙基元,就是新的越位行。
關鍵字 let、if 和 module 中的第一個字元,就是新的越位行。
下列程式碼範例將說明縮排規則。 範例中的幾個 Print 陳述式都依賴縮排,以便與適當內容產生關聯。 每次縮排移動時,原來的內容就會被推出堆疊,使程式回到上一個內容。 因此,這個迴圈每次執行到結尾時都會印出一個空格,而 "Done!" 卻只列印一次,原因在於該行程式碼不符原本的越位縮排,所以這行程式並不是迴圈的一部分。 列印字串 "Top-level context" 則不屬於此函式的功能。 因此會在呼叫函式之前,於靜態初始設定期間先列印此字串。
let printList list1 =
for elem in list1 do
if elem > 0 then
printf "%d" elem
elif elem = 0 then
printf "Zero"
else
printf "(Negative number)"
printf " "
printfn "Done!"
printfn "Top-level context."
printList [-1;0;1;2;3]
輸出如下。
Top-level context
(Negative number) Zero 1 2 3 Done!
將較長的程式碼切分成超過一行時,第一行之後的每一行,其縮排距離都必須比所屬語言建構的縮排距離更遠。 例如,函式引數的縮排距離必須比函式名稱的第一個字元更長,如下列程式碼所示。
let myFunction1 a b = a + b
let myFunction2(a, b) = a + b
let someFunction param1 param2 =
let result = myFunction1 param1
param2
result * 100
let someOtherFunction param1 param2 =
let result = myFunction2(param1,
param2)
result * 100
這些規則有些例外,將於下一節說明。
模組中的縮排
區域模組中的程式碼必須相對於此模組縮排,但是最上層模組中的程式碼則不必縮排。 命名空間項目不必縮排。
下列程式碼範例將對此進行說明。
// Program1.fs
// A is a top-level module.
module A
let function1 a b = a - b * b
// Program2.fs
// A1 and A2 are local modules.
module A1 =
let function1 a b = a*a + b*b
module A2 =
let function2 a b = a*a - b*b
如需詳細資訊,請參閱模組 (F#)。
基本縮排規則的例外
如上一節所述,一般規則是多行建構中的程式碼必須相對於建構第一行的縮排來縮排,而且建構結尾是由發生第一次越位行時所決定。 判定某段內容結束的規則,有一種例外情形,就是某些含有多個部份的建構,例如 try...with 運算式、if...then...else 運算式,以及使用 and 語法宣告相互遞迴函式或型別。 您可以在運算式起始語彙基元的相同層次縮排後面部分 (例如 if...then...else 運算式中的 then 和 else);但是這表示相同建構的下一個部分,而不表示建構結尾。 因此,if...then...else 運算式可以如下列程式碼範例一般來撰寫。
let abs1 x =
if (x >= 0)
then
x
else
-x
越位規則的例外只適用於 then 和 else 關鍵字。 因此,雖然將 then 和 else 的縮排距離延長並不算錯誤,但是若未縮排 then 區塊中的幾行程式碼則會產生警告。 這會在下列幾行程式碼中說明。
// The following code does not produce a warning.
let abs2 x =
if (x >= 0)
then
x
else
-x
// The following code is not indented properly and produces a warning.
let abs3 x =
if (x >= 0)
then
x
else
-x
else 區塊中的程式碼還適用另一項特殊規則。 上一個範例中的警告只會發生於 then 區塊中的程式碼,而不會發生於 else 區塊中的程式碼。 如此一來,您可以在函式開頭撰寫會檢查各種條件的程式碼,而不需強制縮排函式的其餘程式碼 (可能位於 else 區塊中)。 因此,您可以撰寫下列程式碼,而不會產生警告。
let abs4 x =
if (x >= 0) then x else
-x
當一行程式碼的縮排距離不如上一行時內容便結束,此規則的另一個例外是中置運算子,例如 + 和 |>。 以中置運算子起始的程式碼行可以在一般位置的前 (1 + oplength) 欄開始,而不會觸發內容結尾,其中 oplength 是構成運算子的字元數目。 這會使運算子後面的第一個語彙基元對齊上一行。
在下列程式碼範例中,+ 符號可以在比上一行少兩欄的位置縮排。
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2
+ arg3 + arg4
雖然縮排通常隨著巢狀層次升高而增加,但是編譯器允許數個建構將縮排重設為較低的欄位置。
允許重設欄位置的建構如下:
匿名函式的主體。 在下列程式碼中,Print 運算式的起始位置比 fun 關鍵字更靠左側。 不過,這一行不可以在上一個縮排層次開頭左邊 (也就是 List 中 L 的左邊) 的欄位置起始。
let printListWithOffset a list1 = List.iter (fun elem -> printfn "%d" (a + elem)) list1
括在括號中的建構,或是以 if...then...else 運算式之 then 或 else 區塊中的 begin 和 end 封入的建構,前提是縮排不小於 if 關鍵字的欄位置。 此例外允許於 then 或 else 後面行尾使用左括號或 begin 的程式碼撰寫風格。
以 begin...end、{...}、class...end 或 interface...end 分隔之模組、類別、介面和結構的主體。 這允許型別定義的起始關鍵字與型別名稱位於同一行的風格,而不強制整個主體比起始關鍵字的縮排距離更長。
type IMyInterface = interface abstract Function1: int -> int end