函式

函式是所有程式設計語言的基礎程式執行單位。 如同其他語言,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,引數是類型為 intx,函式主體為 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 運算式,而不定義具名函式 incrementmul,如下所示:

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# 中的函式可以從其他函式組合。 兩個函式 function1function2 會組合成另一個函式,表示先套用 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

多載函式

您可以多載類型的方法,但不是函式的方法。 如需詳細資訊,請參閱方法

另請參閱