函式 (F#)
函式是所有程式設計語言的基礎程式執行單位。 如同其他語言,F# 函式有名稱、可以有參數並且接受引數,而且也有主體。 F# 也支援函式程式設計建構,例如將函式視為值、在運算式中使用不具名函式、複合函式以形成新函式、局部調用函式,以及透過部分套用函式引數隱含定義函式。
您可以使用 let 關鍵字定義函式,如果是遞迴函式,則可以使用 let rec 關鍵字組合來定義函式。
// Non-recursive function definition.
let [inline] function-name parameter-list [ : return-type ] = function-body
// Recursive function definition.
let rec function-name parameter-list = recursive-function-body
備註
function-name 是表示函式的識別項。 parameter-list 由以空格分隔的連續參數所組成。 您可以指定每個參數的明確型別,如<參數>一節所述。 如果未指定特定的引數型別,編譯器會嘗試從函式主體推斷型別。 function-body 由運算式組成。 函式主體通常是由複合運算式組成,其中包含數個會產生最終運算式做為傳回值的運算式。 return-type 是選擇性,包含後面接著型別的冒號。 如果您沒有明確指定傳回值的型別,則編譯器會從最終運算式判斷傳回型別。
簡單的函式定義如下:
let f x = x + 1
在上述範例中,函式名稱為 f,引數是型別為 int 的 x,函式主體為 x + 1,而傳回值的型別為 int。
內嵌規範是對編譯器的提示,表示函式為小型函式,而且函式的程式碼可以整合至呼叫端的主體中。
範圍
在模組範圍以外的任何範圍層級,重複使用值或函式名稱並不是錯誤。 如果重複使用名稱,後面宣告的名稱會遮蔽前面宣告的名稱。 不過,在模組中的最上層範圍,名稱必須是唯一。 例如,下列程式碼出現在模組範圍時會產生錯誤,但在函式內時則不會:
let list1 = [ 1; 2; 3]
// Error: duplicate definition.
let list1 = []
let function1 =
let list1 = [1; 2; 3]
let list1 = []
list1
但是,下列程式碼在任何範圍層級都可以使用:
let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1.
let list1 = [1; 5; 10]
x + List.sum list1
參數
參數名稱必須列於函式名稱後面。 您可以指定參數的型別,如下列範例所示:
let f (x : int) = x + 1
如果您指定型別,請將它放在參數名稱之後,並且以冒號來分隔型別與名稱。 如果您省略參數的型別,編譯器便會推斷參數型別。 例如,在下列函式定義中,引數 x 經推斷為 int 型別,因為 1 是 int 型別。
let f x = x + 1
不過,編譯器會嘗試將函式盡可能推斷成為泛型。 例如,請注意下列程式碼:
let f x = (x, x)
函式會從任何型別的一個引數建立 Tuple。 因為沒有指定型別,所以函式可以與任何引數型別搭配使用。 如需詳細資訊,請參閱自動產生 (F#)。
函式主體
函式主體可以包含區域變數和函式的定義。 這類變數和函式是在目前函式主體的範圍內,而不是在主體以外。 啟用輕量型語法選項時,必須使用縮排來表示定義是在函式主體中,如下列範例所示:
let cylinderVolume radius length =
// Define a local value pi.
let pi = 3.14159
length * pi * radius * radius
如需詳細資訊,請參閱程式碼格式化方針 (F#)與詳細語法 (F#)。
傳回值
編譯器會使用函式主體中的最終運算式,判斷傳回值和型別。 編譯器可能會從先前的運算式推斷最終運算式的型別。 在上節示範的 cylinderVolume 函式中,pi 的型別是從常值 3.14159 的型別經判斷為 float。 編譯器會使用 pi 的型別,判斷運算式 h * pi * r * r 的型別為 float。 因此,函式的整體傳回型別為 float。
若要明確指定傳回值,請撰寫如下的程式碼:
let cylinderVolume radius length : float =
// Define a local value pi.
let pi = 3.14159
length * pi * radius * radius
撰寫如上所示的程式碼時,編譯器會將 float 套用至整個函式。如果您也要將它套用至參數型別,請使用下列程式碼:
let cylinderVolume (radius : float) (length : float) : float
呼叫函式
您可以指定函式名稱,後面接著空格,再接著以空格分隔的任何引數來呼叫函式。 例如,若要呼叫函式 cylinderVolume 並將結果指派給值 vol,您可以撰寫下列程式碼:
let vol = cylinderVolume 2.0 3.0
部分套用引數
如果您提供的引數數目比指定的數目更少,必須建立需要其餘引數的新函式。 這個處理引數的方法稱為「局部調用」(Currying),是 F# 這類函式程式設計語言的特色。 例如,假設您要使用兩種大小的管子,其中一個半徑為 2.0,另一個半徑為 3.0。 您可以建立會判斷管子容量的函式,如下所示:
let smallPipeRadius = 2.0
let bigPipeRadius = 3.0
// These define functions that take the length as a remaining
// argument:
let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius
接著視需要為兩個不同大小的各種管子長度提供其他引數:
let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2
遞迴函式
「遞迴函式」(Recursive Function) 是會自我呼叫的函式。 您必須在 let 關鍵字後面指定 rec 關鍵字來使用遞迴函式, 並且從函式主體中叫用遞迴函式,就像叫用任何函式呼叫一樣。 下列遞迴函式會計算第 n 個 Fibonacci 數字。 Fibonacci 數字序列自古聞名,此序列中的每個連續數字都是前兩個數字的總和。
let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)
如果不謹慎撰寫遞迴函式,而且也不了解特殊技巧 (例如累加值和接續符號的用法),某些遞迴函式便可能會使程式堆疊溢位,或導致執行時沒有效率。
函式值
在 F# 中,所有函式都視為值,而實際上它們稱為「函式值」(Function Value)。 由於函式都是值,因此可以做為其他函式的引數,或在其他會用到這些值的內容中使用。 以下是接受函式值做為引數的函式範例:
let apply1 (transform : int -> int ) y = transform y
您可以使用 -> 語彙基元來指定函式值的型別。 在此語彙基元左邊是引數的型別,右邊則是傳回值。 在上述範例中,apply1 函式會接受 transform 函式做為引數,其中 transform 是接受整數並傳回另一個整數的函式。 在下列範例程式碼中,會示範 apply1 的用法:
let increment x = x + 1
let result1 = apply1 increment 100
在上述程式碼執行之後,result 的值會是 101。
多個引數是以連續 -> 語彙基元分隔,如下列範例所示:
let apply2 ( f: int -> int -> int) x y = f x y
let mul x y = x * y
let result2 = apply2 mul 10 20
結果為 200。
Lambda 運算式
「Lambda 運算式」(Lambda Expression) 是不具名函式。 在上述範例中,您可以改用 Lambda 運算式,而不定義具名函式 increment 和 mul,如下所示:
let result3 = apply1 (fun x -> x + 1) 100
let result4 = apply2 (fun x y -> x * y ) 10 20
您可以透過 fun 關鍵字定義 Lambda 運算式。 Lambda 運算式類似函式定義,但是它使用 -> 語彙基元分隔引數清單與函式主體,而不是 = 語彙基元。 如同一般函式定義,您可以明確指定引數型別,也可以讓編譯器來推斷引數型別,而且 Lambda 運算式的傳回型別是從主體中最後一個運算式的型別推斷。 如需詳細資訊,請參閱Lambda 運算式:fun 關鍵字 (F#)。
函式複合和管線
F# 中的函式可從其他函式複合。 如下列範例所示,兩個函式 function1 和 function2 會複合為另一個函式,表示先套用 function1,接著套用 function2:
let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100
結果為 202。
管線可讓函式呼叫鏈結在一起成為連續作業。 管線運作方式如下:
let result = 100 |> function1 |> function2
結果同樣是 202。
複合運算子會採用兩個函式並傳回函式;相反地,管線運算子接受函式和引數和傳回值。 下列程式碼範例會顯示差異顯示管線和複合運算子之間的差異在函式簽章和使用方式。
// Function composition and pipeline operators compared.
let addOne x = x + 1
let timesTwo x = 2 * x
// Composition operator
// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3
let Compose2 = addOne >> timesTwo
// Backward composition operator
// ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
let Compose1 = addOne << timesTwo
// Result is 5
let result1 = Compose1 2
// Result is 6
let result2 = Compose2 2
// Pipelining
// Pipeline operator
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x
// Backward pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo
// Result is 5
let result3 = Pipeline1 2
// Result is 6
let result4 = Pipeline2 2
多載函式
您可以多載不是型別,但是函式的方法。 如需詳細資訊,請參閱方法 (F#)。