將 Using Statement 重構成 Using() Function
C# 有個著名的 using
statement,對於實踐 IDisposable
的物件特別好用,但 using
是個 statement,在 Imperative 世界沒問題,但在 Functional 世界,statement 就類似 句點
,讓我們無法繼續 Pipeline 或對其他 function 做 Compose,我們能否比照將 foreach
statement 重構成 ForEach()
function,也將 using
statement 重構成 using()
function 呢 ?
Version
macOS High Sierra 10.13.6
.NET Core 2.1
C# 7.2
F# 4.5
Rider 2018.1.4
C# 之 Using Statement
1 | using System; |
StreamReader
是個典型實踐 IDisposable
的物件,所以在使用時都會使用 using statement
包起來,等離開 {}
scope 時,自動呼叫 Dispose()
釋放 resource。
這些都是我們都習慣的 C#。
但 using
是 statement,在 Imperative 世界沒問題,反正程式碼都是一行一行循序執行。
但在 Functional 世界,我們要求 code 要 Pipeline,要 Compose,所以 FP 喜歡使用 expression,不喜歡 statement。
Statemet 就類似 句點
,讓所有的 Pipeline 都中斷了。
其實仔細看 using
statement,其實包含幾個部分:
- Setup : 獲得 resource
- Body : 執行 resource
- Teardown : 釋放 resource
其中 using
statement 就是幫我們做 teardown 部分。
因此我們可以自己寫一個 Using()
function,將 setup 與 body 傳入 Using()
。
C# 之 Using() Function
1 | using System.IO; |
10 行
1 | Using(new StreamReader("TestFile.txt"), ReadFile) |
使用 Using()
function,將 setup 傳入第一個參數,將 body 傳入第二個參數。
由於 ReadFile()
回傳為 string
,因此 Using()
也是回傳 string
,這樣就可以使用 Pipeline 方式 WriteLine()
直接印出。
13 行
1 | string ReadFile(StreamReader streamReader) => streamReader.ReadToEnd(); |
Body 以 local function 定義。
至於 Using()
與 WriteLine()
怎麼來的呢 ? 是我們自己寫的 Higher Order Function。
1 | using System; |
第 7 行
1 | public static R Using<TDisp, R>(TDisp disposable, Func<TDisp, R> f) where TDisp : IDisposable |
自己寫一個 Using()
HOF,第一個參數傳入 IDisposable
物件,第二個參數傳入 body function。
12 行
1 | public static void WriteLine(this string data) |
自己為 string
加上 WriteLine()
Extension Method,這就就可以對 string
繼續 Pipeline 印出。
C# 為了讓
using
用起來更 FP,我們必須自己實作Using()
與WriteLine()
,但在 Functional First 的 F#,除了提供 Imperative 的use
外,也提供了 Functional 的using()
,我們完全不用自己另外實作
F# 之 Use Bind
1 | open System.IO |
F# 之 use
類似於 let
,差別是 use
在離開 function 就會呼叫 Dispose()
,不需特別加上 {}
縮排一層。
由於 readFromFile()
回傳 string
,可以直接 Pipeline 接內建的 printf()
。
但 use
仍然是個 statement。
F# 之 Using() Function
1 | open System.IO |
第 6 行
1 | let readFromFile (fileName: string) = |
改用 F# 內建的 using()
,第一個參數傳入 IDisposable
物件,第二個參數傳入 body function,其實跟自己用 C# 實作的 Using()
是一樣的。
第 3 行
1 | let readFile (streamReader: StreamReader) = |
定義 body function。
由於
using()
與printf()
都是 F# 內建,因此我們就不必再自己實作了
Conclusion
- 將 C# 由
using
statement 改成using()
function,乍看之下意義不大;但若去看 F# 同時提供use
statement 與using()
function 時,就可看出 F# 的用心良苦,同時支援了 Imperative 與 Functional 兩種 paradigm - 由於 F# 每個 function 都是 composable,因此我們就不必再自已寫
WriteLine()
了,直接printf()
就可以 pipeline 起來
Sample Code
Reference
Enrico Buonanno, Functional Programming in C#