共用方式為


Functions

函式是任何程式設計語言中程序執行的基本單位。 如同其他語言,F# 函式具有名稱、可以有參數和接受自變數,以及具有主體。 F# 也支援函式程式設計建構,例如在運算式中使用未命名的函式、函式組合來形成新函式、curried 函式,以及函式自變數的部分應用來隱含定義函式。

您可以使用 關鍵詞來定義函 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

備註

式名稱 是代表函式的識別碼。 parameter-list 是由以空格分隔的後續參數所組成。 您可以指定每個參數的明確類型,如 Parameters 區段所述。 如果您未指定特定的自變數類型,編譯程式會嘗試從函式主體推斷類型。 函式 主體 是由表達式所組成。 組成函式主體的表達式通常是復合表達式,其中包含一些表達式,最終以傳回值為最終表達式。 return-type 是冒號,後面接著類型,而且是選擇性的。 如果您未明確指定傳回值的型別,編譯程式會從最終表達式判斷傳回型別。

簡單的函式定義如下所示:

let f x = x + 1

在上述範例中,函式名稱為 f,引數是類型為 xint,函式主體為 x + 1,而傳回值的類型為 int

函式可以標示 inline為 。 如需 的相關信息 inline,請參閱 內嵌函式

Scope

在模組範圍以外的任何範圍層級上,重複使用值或函式名稱並非錯誤。 如果您重複使用名稱,稍後宣告的名稱會遮蔽稍早宣告的名稱。 不過,在模組的最上層範圍中,名稱必須是唯一的。 例如,下列程式代碼會在模組範圍出現時產生錯誤,但在函式內出現時則不會產生錯誤:

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。 因為未指定類型,因此函式可以搭配任何自變數類型使用。 如需詳細資訊,請參閱 自動一般化

函式主體

函式主體可以包含局部變數和函式的定義。 這類變數和函式位於目前函式主體的範圍中,但未位於其外部。 您必須使用縮排來指出定義位於函式主體中,如下列範例所示:

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 * radiusfloat。 因此,函式的整體傳回型態為 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 並將結果指派給 value 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

遞歸函式

遞迴函 式是自行呼叫的函式。 它們會要求您指定 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 運算式,而不是定義具名函式 遞增mul,如下所示:

let result3 = apply1 (fun x -> x + 1) 100

let result4 = apply2 (fun x y -> x * y) 10 20

您可以使用 關鍵詞來定義 Lambda 運算式 fun 。 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

多載函式

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

另請參閱