1. 程式人生 > 實用技巧 >F# 函數語言程式設計之 - 隱藏運算

F# 函數語言程式設計之 - 隱藏運算

“隱藏運算” 是我發明的詞,它的正式名稱是 “computation expressions”。

但 “computation expressions” 這個名稱實在讓人非常費解,也不能反映它的作用,不是一個好名稱。

它的作用是在背後對兩個表示式進行一些操作,讓表示式們表面上看起來簡單。

請看例子:

let divideBy bottom top =
    match bottom with
    | 0 -> None
    | _ -> Some <| top/bottom

let maybe = new MaybeBuilder()

let divideWorkflow init x y z =
    maybe {
        let! a = init |> divideBy x
        let! b = a |> divideBy y
        let! c = b |> divideBy z
        return c
    }

divideWorkflow 12 3 2 1  // Some(2)
divideWorkflow 12 3 0 1  // None

請看那幾個 let!, 表面上是 init 除以 x, 得出結果 a 再除以 y, 以此類推。這個表面上的邏輯非常清晰,但如果不是背後隱藏了一些處理(意思是,如果用 let 而不是用 let!),這個程式碼是跑不通的。因為 init 除以 x 得出來的 a 不是一個數字(divideBy 的返回值是 Option, 不是 int), 因此如果讓 a 直接除以 y 會產生型別不匹配的錯誤。

簡單來說,let! 表示 “computation expressions”, 也就是意味著在背後隱藏著一些我們表面上看不見的運算。

那麼,這個背後的運算具體是什麼?我們可以看到,全部 let! 都在 maybe{...}

裡面,而 maybe 是由 MaybeBuilder 生成的,顯然,關鍵在於 MaybeBuilder 的定義:

type MaybeBuilder() =
    member this.Bind(x, f) =
        match x with
        | None -> None
        | Some a -> f a

    member this.Return(x) = Some x

如上所示,這個 MaybeBuilder 的定義不是系統自帶的,要我們自己寫。可以看到,這個 “背後的程式碼” 對 x:Option 進行處理,如果是 None 則直接返回 None, 並且由於這個 None 會傳遞給下一個 let!

, 因此可以預見一旦遇到 None, 後續的 f 就一律不會被運算,最終結果返回 None。如果是 Some 則從 Some 中提出取 a:int 並執行 f(a), 因此,經過這個背後的處理,表面上的 a |> divideBy y 就不會出錯了。

“computation expressions” 可以把囉嗦的輔助語句隱藏在背後,讓表面上的核心語句看起來很簡潔,因此很好用,是 F# 裡很常用的一種技術。但由於它是 “隱式的” 而不是 “顯示的”, 所以理解起來需要在腦子裡多繞幾個彎,本文只是簡單地介召了它的主要特點,想了解得更詳細請看以下資料:

  1. https://fsharpforfunandprofit.com/posts/computation-expressions-intro/
  2. https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions