共用方式為


F 中的功能性程序設計概念簡介#

功能程式設計是一種程式設計樣式,強調函式和不可變數據的使用。 型別化函數式程式設計是指將函數式程式設計與靜態類型結合,例如 F# 等。 一般而言,在功能性程序設計中會強調下列概念:

  • 函式作為您使用的主要建構
  • 表達式而非語句
  • 不可變值優於變數
  • 宣告式程式設計優於命令式程式設計

在此系列中,您將探索使用 F# 進行功能性程式設計的概念和模式。 一路上,您也會學習一些 F# 。

術語

功能程序設計與其他程式設計範例一樣,隨附您最終需要學習的詞彙。 以下是一些常見的詞彙,您會隨時看到:

  • 函式 - 函式是一種建構,會在指定輸入時產生輸出。 更正式地說,它會將項目從一個集合 映射 到另一個集合。 這種形式主義以多種方式轉化為具體的,尤其是在使用在資料集上運行的函式時。 這是功能程序設計中最基本的(和重要)概念。
  • 表達式 - 運算式 是產生值的程式代碼中的建構。 在 F# 中,此值必須系結或明確忽略。 表達式可以輕易透過函式呼叫來取代。
  • 純潔性 - 純潔 性是函式的屬性,因此其傳回值在相同的自變數中一律相同,而且其評估沒有副作用。 純函式完全取決於其自變數。
  • 引用透明度 - 引用透明度 是表達式的屬性,因此可以將其取代為其輸出,而不會影響程序的行為。
  • 不變性 - 不變性表示無法就地變更值。 這與變數形成鮮明對比,變數可以就地變更。

範例

下列範例示範這些核心概念。

功能

函式程式設計中最常見的和基本建構是函式。 以下是將 1 新增至整數的簡單函式:

let addOne x = x + 1

其類型簽章如下所示:

val addOne: x:int -> int

簽名可以解讀為「addOne 接受一個名為 intx,並會產生 int」。 更正式的是, addOne 將一組整數的值 對應 到整數集。 令牌 -> 表示此對應。 在 F# 中,您通常可以查看函式簽章,以瞭解其用途。

那麼,為什麼簽章很重要? 在具型別函數式程式設計中,函式的實作通常比類型簽章更不重要! 在運行時間中,addOne 將值 1 加入整數的事實很有趣,但在您建構程式時,它能接受並返回 int 的事實才是決定您如何實際使用這個函式的關鍵。 此外,一旦您正確使用此函式(相對於其類型簽章),診斷任何問題只能在函式主體 addOne 內完成。 這是具型別功能程序設計背後的動力。

表達方式

表達式是評估為值的結構。 相較於執行動作的語句,可以考慮表達式執行可傳回值的動作。 表達式幾乎一律用於功能性程序設計,而不是語句。

請考慮上一個函式: addOne。 的主體 addOne 是表達式:

// 'x + 1' is an expression!
let addOne x = x + 1

這是這個表達式的結果,可定義函式的結果類型 addOne 。 例如,組成此函式的表示式可以變更為不同類型的 ,例如 string

let addOne x = x.ToString() + "1"

函式的定義現在是:

val addOne: x:'a -> string

由於在 F# 中,任何類型都可以調用 ToString(),所以 x 的類型已成為泛型類型(稱為 自動一般化),而結果類型為 string

表達式不只是函式的主體。 您可以有一些運算式,它們可產生供其他地方使用的值。 常見的是 if

// Checks if 'x' is odd by using the mod operator
let isOdd x = x % 2 <> 0

let addOneIfOdd input =
    let result =
        if isOdd input then
            input + 1
        else
            input

    result

表達式 if 會產生稱為 result的值。 請注意,您可以完全省略 result ,讓 if 表達式成為函式的 addOneIfOdd 主體。 要記住表達式的關鍵是它們會產生值。

有一種特殊類型unit,在沒有任何項目返回時使用。 例如,請考慮這個簡單的函式:

let printString (str: string) =
    printfn $"String is: {str}"

簽章看起來像這樣:

val printString: str:string -> unit

unit 表明沒有傳回的實際值。 當您有一個例程必須「執行工作」,但因為該工作而沒有傳回任何值時,這非常有用。

這與命令式程序設計形成鮮明對比,其中對等 if 建構是語句,而產生值通常是使用變動變數來完成。 例如,在 C# 中,程式代碼可能會像這樣撰寫:

bool IsOdd(int x) => x % 2 != 0;

int AddOneIfOdd(int input)
{
    var result = input;

    if (IsOdd(input))
    {
        result = input + 1;
    }

    return result;
}

值得注意的是,C# 和其他 C 樣式語言確實支援 三元表示式,以允許以運算式為基礎的條件式程序設計。

在功能性程序設計中,使用語句來變動值並不罕見。 雖然某些功能語言支援語句和突變,但在功能性程序設計中使用這些概念並不常見。

純函式

如先前所述,純函式是下列函式:

  • 始終為相同輸入評估為相同值。
  • 沒有副作用。

在這種情境下,去理解數學函數是很有幫助的。 在數學中,函式只取決於其自變數,而且沒有任何副作用。 在數學函 f(x) = x + 1式中,的值 f(x) 只取決於的值 x。 在函數式程式設計中,純函式也是如此。

撰寫純函式時,函式必須只取決於其自變數,而且不會執行任何會導致副作用的動作。

以下是非純函式的範例,因為它相依於全域、可變動的狀態:

let mutable value = 1

let addOneToValue x = x + value

addOneToValue 式顯然不完美,因為 value 可以隨時變更為具有與 1 不同的值。 根據全域值而定的這種模式,在功能性程序設計中應避免。

以下是非純函式的另一個範例,因為它會執行副作用:

let addOneToValue x =
    printfn $"x is %d{x}"
    x + 1

雖然此函式不相依於全域值,但它會將的值 x 寫入程序輸出。 雖然這樣做原本沒有任何問題,但它確實表示函式不是純粹的。 如果您的程式的另一個部分相依於程式外部的專案,例如輸出緩衝區,則呼叫此函式可能會影響程式的其他部分。

移除 語句會使 printfn 函式變成純正:

let addOneToValue x = x + 1

雖然這個函式本質上並不比使用語句的舊版本更好,但它確實能保證此函式只會傳回一個值。 呼叫此函式的任何次數會產生相同的結果:它只會產生值。 純粹所帶來的可預測性是許多函數式程式設計師追求的目標。

不變性

最後,具型別功能程序設計的最基本概念之一是不變性。 在 F# 中,所有值預設都是不可變的。 這表示除非明確將它們標示為可變動,否則它們無法就地變動。

在實務上,使用不可變的值意味著您將程序設計方法從「我需要變更某件事」轉變為「我需要產生新值」。

例如,將 1 加入至值表示產生新值,而不是將現有值變動:

let value = 1
let secondValue = value + 1

在 F# 中,下列程式代碼 不會 變動函 value 式;而是會執行相等檢查:

let value = 1
value = value + 1 // Produces a 'bool' value!

某些功能性程序設計語言完全不支援突變。 在 F# 中,它受到支援,但不是值的預設行為。

此概念會進一步延伸至數據結構。 在功能性程序設計中,不可變的數據結構,例如集合(和更多)具有不同於您一開始預期的不同實作。 從概念上講,將項目新增至集合並不會變更集合,它會產生具有附加值的新集合。 實際上,這通常是由不同的數據結構來完成,以便有效率地追蹤值,以便產生適當的數據表示法。

這種處理值和數據結構的樣式非常重要,因為它會強制您處理任何修改某個項目的作業,就好像它建立新版的東西一樣。 這可以在您的程式中保持一致性,例如確保等同性和可比性。

後續步驟

下一節將徹底討論函式,探索在功能程序設計中使用的不同方式。

在 F# 中使用函式深入探討其運用,展示如何在各種情境中應用函式。

進一步閱讀

「思維功能」系列是另一個絕佳的資源,可用來瞭解 F# 的功能程序設計。 其涵蓋以務實且容易閱讀的方式進行功能程序設計的基本概念,使用 F# 功能來說明概念。