函式 (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。
請參閱
其他資源
變更記錄
日期 |
記錄 |
原因 |
---|---|---|
2010 年 5 月 |
修正參數一節中的程式碼範例。 |
內容 Bug 修正。 |
2011 年 4 月 |
新增關於參數和傳回值的說明資訊。 |