共用方式為


函式 (F#)

更新:2010 年 5 月

函式是所有程式設計語言的基礎程式執行單位。 如同其他語言,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#)

如需關於參數的詳細資訊,請參閱參數和引數(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

函式一律傳回一個值。 如果函式不傳回實際值,可以傳回 unit值。 您可以透過多種方法傳回多項資料。 一種方法是傳回 Tuple 值。 如果函式傳回 Tuple 值,您可以在 let 繫結中使用 Tuple 模式將 Tuple 的元素指派給一個以上的值。 以下的程式碼可說明這點。

let firstAndLast list1 =
    (List.head list1, List.head (List.rev list1))

let (first, last) = firstAndLast [ 1; 2; 3 ]
printfn "First: %d; Last: %d" first last

上述程式碼的輸出如下:

  

傳回多個資料片段的另一個方法是使用參考資料格,第三個方法則是使用byref參數。 如需詳細資訊和範例,請參閱參照儲存格 (F#)參數與引數 (F#)

呼叫函式

您可以指定函式名稱,後面接著空格,再接著以空格分隔的任何引數來呼叫函式。 例如,若要呼叫函式 cylinderVolume 並將結果指派給值 vol,您可以撰寫下列程式碼:

let vol = cylinderVolume 2.0 3.0

如果函式以 Tuple 為單一參數,函式呼叫的結尾會是括弧中的清單,看起來像是以其他語言寫成的引數清單。 函式名稱與左括號之間的空格可以省略。 例如,以下是以 Tuple 為參數的 cylinderVolume函式。

let cylinderVolume(radius, length) =
    let pi = 3.14159
    length * pi * radius * radius

使用 Tuple 做為參數,對函式的呼叫看起來像這樣。

let vol = cylinderVolume(2.0, 3.0)

如果函式沒有參數,您可以指定unit值 () 作為引數,如下列程式碼行所示。

initializeApp()

函式名稱本身只是函式值,因此,如果您省略表示 unit 值的括號,則函式只是被參考,不會被呼叫。

部分套用引數

如果您提供的引數數目比指定的數目更少,必須建立需要其餘引數的新函式。 這個處理引數的方法稱為「局部調用」(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。

請參閱

其他資源

值 (F#)

F# 語言參考

變更記錄

日期

記錄

原因

2010 年 5 月

修正參數一節中的程式碼範例。

內容 Bug 修正。

2011 年 4 月

新增關於參數和傳回值的說明資訊。