函式
函式是所有程式設計語言的基礎程式執行單位。 如同其他語言,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
。
函式可以標記為 inline
。 如需 inline
的資訊,請參閱內嵌函式。
範圍
在模組範圍以外的任何範圍層級,重複使用值或函式名稱並非錯誤。 若重複使用名稱,後面宣告的名稱會遮蔽前面宣告的名稱。 不過,在模組中的最上層範圍,名稱必須是唯一名稱。 例如,下列程式碼出現在模組範圍時會產生錯誤,但在函式內時則不會:
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)
函式會從任何類型的一個引數建立元組。 因為沒有指定類型,所以函式可以與任何引數類型搭配使用。 如需詳細資訊,請參閱自動產生。
函式主體
函式主體可以包含區域變數和函式的定義。 這類變數和函式是在目前函式主體的範圍內,而不是在主體以外。 您必須使用縮排來表示定義是在函式主體中,如下列範例所示:
let cylinderVolume radius length =
// Define a local value pi.
let pi = 3.14159
length * pi * radius * radius
傳回值
編譯器會使用函式主體中的最終運算式,來判斷傳回值和類型。 編譯器可能會從先前的運算式推斷最終運算式的類型。 在上節示範的 cylinderVolume
函式中,pi
的類型是從常值 3.14159
的類型經判斷為 float
。 編譯器會使用 pi
的類型,判斷運算式 length * pi * radius * radius
的類型為 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
部分套用引數
若您提供的引數數目比指定的數目更少,則必須建立需要其餘引數的新函式。 這個處理引數的方法稱為「局部調用」,是 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
遞迴函式
「遞迴函式」是會自我呼叫的函式。 您必須在 let 關鍵字後面指定 rec 關鍵字來使用遞迴函式。 請從函式主體中叫用遞迴函式,就像叫用任何函式呼叫一樣。 下列遞迴函式會計算第 n個 Fibonacci 數字。 Fibonacci 數字序列自古聞名,此序列中的每個連續數字都是前兩個數字的總和。
let rec fib n =
if n < 2 then 1 else fib (n - 1) + fib (n - 2)
若不謹慎撰寫遞迴函式,而且也不了解特殊技巧 (例如尾遞迴、累計值和接續符號的用法),某些遞迴函式便可能會使程式堆疊溢位,或導致執行時沒有效率。
函式值
在 F# 中,所有函式都視為值,而實際上它們稱為「函式值」。 由於函式都是值,因此可以作為其他函式的引數,或在其他會用到這些值的內容中使用。 以下是接受函式值作為引數的函式範例:
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 運算式,而不定義具名函式 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
關鍵字。
Pipelines
在 F# 中處理資料時,會廣泛使用管道運算子 |>
。 此運算子可讓您以彈性方式建立函式的「管道」。 管道可讓函式呼叫鏈結在一起成為連續作業:
let result = 100 |> function1 |> function2
下列範例會逐步解說如何使用這些運算子來建置簡單的函式管道:
/// Square the odd values of the input and add one, using F# pipe operators.
let squareAndAddOdd values =
values |> List.filter (fun x -> x % 2 <> 0) |> List.map (fun x -> x * x + 1)
let numbers = [ 1; 2; 3; 4; 5 ]
let result = squareAndAddOdd numbers
結果為 [2; 10; 26]
。 上一個範例使用清單處理函式,示範如何在建置管道時使用函式來處理資料。 管道運算子本身定義於 F# 核心程式庫中,如下所示:
let (|>) x f = f x
函式複合
F# 中的函式可以從其他函式組合。 兩個函式 function1 和 function2 會組合成另一個函式,表示先套用 function1,接著套用 function2:
let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100
結果為 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
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo
// Backward pipeline operator
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x
// Result is 5
let result3 = Pipeline1 2
// Result is 6
let result4 = Pipeline2 2
多載函式
您可以多載類型的方法,但不是函式的方法。 如需詳細資訊,請參閱方法。