Refactoring to LINQ => ForEach

栏目: ASP.NET · 发布时间: 6年前

内容简介:LINQ 是 C# 3 實現 FP 重要里程碑,提供大量的 Operator,讓我們以 Pure Function 將 data 以 dataflow 方式實現。本系列將先以 Imperative 實作,然後再重構成 FP,最後再重構成 LINQ Operator,並參考 LINQ source code 的實現方式。首先從最基本的macOS High Sierra 10.13.6

LINQ 是 C# 3 實現 FP 重要里程碑,提供大量的 Operator,讓我們以 Pure Function 將 data 以 dataflow 方式實現。本系列將先以 Imperative 實作,然後再重構成 FP,最後再重構成 LINQ Operator,並參考 LINQ source code 的實現方式。

首先從最基本的 ForEach Operator 談起。

Version

macOS High Sierra 10.13.6

.NET Core 2.1

C# 7.2

Rider 2018.1.4

User Story

在 List 中有一堆名字,想要在 console 顯示每個名字。

using System.Collections.Generic;

namespace ConsoleApp
{
    class Program
    {
        static void Main()
        {
            var names = new List<string>
            {
                "Ben",
                "Jafar",
                "Matt",
                "Priya",
                "Brian"
            };
        }
    }
}

Imperative : for

using System;
using System.Collections.Generic;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var names = new List<string>
            {
                "Ben",
                "Jafar",
                "Matt",
                "Priya",
                "Brian"
            };

            for (var counter = 0; counter < names.Count; counter++)
            {
                Console.WriteLine(names[counter]);
            }
        }
    }
}

19 行

for (var counter = 0; counter < names.Count; counter++)
{
    Console.WriteLine(names[counter]);
}

最直覺的方式 (其實應該說被制約的方式), 就是透過 List 的 Indexer 將 List 的 item 取出來,並透過 for loop 去執行 Console.WriteLine()

Imperative : foreach

Refactoring to LINQ => ForEach

此時 Rider 已經提出警告,建議改用 foreach

Refactoring to LINQ => ForEach

  1. 按熱鍵 ⌥ + ↩ ,選擇 Convert to foreach
using System;
using System.Collections.Generic;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var names = new List<string>
            {
                "Ben",
                "Jafar",
                "Matt",
                "Priya",
                "Brian"
            };

            foreach (var name in names)
            {
                Console.WriteLine(name);
            }
        }
    }
}

19 行

foreach (var name in names)
{
    Console.WriteLine(name);
}

重構成 foreach 後,就不必使用 counter 變數,也不用考慮 counter++ 寫錯, for loop 的 counter 控制,是常見的 bug 來源,所以重構成 foreach 絕對比 for 來得好。

foreach 的語意也比 for 清楚 ,因為不用看到 counter 實作細節。

FP : Higher Order Function

實務上這種 foreach 天天都要用到,但用 foreach 這種 statement 寫法,重複使用能力為 0,就每天都要不斷的寫 foreach

若我們能將 foreach 抽成 ForEach() Higher Order Function,我們就能不斷 reuse ForEach() ,只要將不同的商業邏輯以 function 傳進 ForEach() 即可。

using System;
using System.Collections.Generic;

namespace ConsoleApp
{
    static class Program
    {
        static void Main(string[] args)
        {
            var names = new List<string>
            {
                "Ben",
                "Jafar",
                "Matt",
                "Priya",
                "Brian"
            };

            names.MyForEach(name => Console.WriteLine(name));
        }
        
        private static void MyForEach<T>(this List<T> list, Action<T> action)
        {
            foreach (var iter in list)
            {
                action(iter);
            }
        }
    }
}

22 行

private static void MyForEach<T>(this List<T> list, Action<T> action)
{
    foreach (var iter in list)
    {
        action(iter);
    }
}

自己以 MyForEach() 實作出 foreach 的 Higher Order Function 版本。

第 9 行

names.MyForEach(name => Console.WriteLine(name));

原來的 foreach statement 重構成 MyForEach() Higher Order Function 版本,只要將 Console.WriteLine() 改用 Lambda 傳入 MyForEach() 即可,如此 foreach 就能被 reuse 了。

不過 C# 在此可能感受不到 FP 所謂的 Dataflow 與 Pipeline,就語意而言,只能說我們為 List 寫了一個新 method : MyForEach() ,也就是 C# 所謂的 Extension Method,這符合傳統 C# 的 OOP 思維。

他山之石可以攻錯,我們來看看 F# 語法。

let names = ["Ben"; "Jafar"; "Matt"; "Priya"; "Brian"]

let MyForEach action list = 
    for iter in list do
        action iter
names 
|> MyForEach (printfn "%A ")

names 為 List, myForEach() 為自己實作的 ForEach() Higher Order function,若不懂 F# 語法先略過細節沒關係。

|> 為 pipline,所以可以明顯看出其語意為 names data 以 Dataflow 與 Pipeline 方式傳給 myForEach() 執行 printf()

但 C# 並沒有特別提供 |> 這種 pipeline 符號,而是繼續使用 OOP 的 .

. 雖然方便,但心裡要知道,這裡的 . 並不是 OOP 的 method,而是 FP 的 Dataflow 與 Pipeline,只是也使用了 . 為符號,其本質是 F# 的 |>. 算是 |> 的 syntax sugar。

private static void MyForEach<T>(this List<T> list, Action<T> action)
{
    foreach (var iter in list)
    {
        action(iter);
    }
}

再舉例另外一個例子證明 . 並非 OOP 的 method,而是 FP 的 Pipeline。

我們實現 MyForEach() 時,並不是寫在 List class 內,而是透過 static method + this 參數成為 Extenstion Method,也就是 List data 與 MyForEach() logic 是徹底分離,而非如 OOP 將 data 與 logic 寫在 List class 內,也再次證明 Extension Method 是 C# 實踐 Pipeline 的手法,而 . 只是 syntax sugar,其本質是 FP 的 Pipeline。

IEnumrable

Refactoring to LINQ => ForEach

當初我們只是想為了 List 重構,所以 Extension Method 的型別只用了 List,Rider 提出建議: List<T> 其實可以重構成 IEnumerable<T> ,如此 MyForEach() 重複使用能力更高。

Refactoring to LINQ => ForEach

  1. 將 cursor 放在 List 上,按熱鍵 ⌥ + ↩ ,選擇 Make parameter type IEnumerable<T>

Refactoring to LINQ => ForEach

  1. List<T> 重構成 IEnumerable<T>

Method Group

Refactoring to LINQ => ForEach

Rider 建議將 Lambda 重構成 Method Group。

MyForEach() 後接 Lambda 天經地義,但若 function 的型別定義的很清楚,讓 compiler 可以找到正確 signature 的 Overloading method,則不用寫 Lambda,傳入 method 名稱即可,這就是 Method Group,會讓 FP 寫法更加精簡。

MyForEach() 很明確定義了 Action<T> ,就只有一個 input 參數的 function,因此 compiler 有足夠的資訊找到 Console.WriteLine() 正確的 Overloading 版本,因此適合重構成 Method Group。

Refactoring to LINQ => ForEach

  1. 將 cursor 放在 WriteLine 上,按熱鍵 ⌥ + ↩ ,選擇 Replace with method group

Refactoring to LINQ => ForEach

  1. 只要傳入 Method 名稱即可,不用寫 Lambda

Using static

其實目前的 Console.WriteLine 已經非常精簡,可讀性也高,但 C# 6 提供了 using static ,可以讓你寫出更 FP 風格的 code。

Refactoring to LINQ => ForEach

  1. 將 cursor 放在 Console 上,按熱鍵 ⌥ + ↩ ,選擇 Import static members

Refactoring to LINQ => ForEach

  1. System.Console 使用 using static
  2. MyForEach() 只要傳入 WriteLine 即可,更像是 function

LINQ : ForEach

其實 ForEach() 這種很普遍的東西,在 LINQ 早已內建,我們改用 LINQ 版本。

Refactoring to LINQ => ForEach

  1. 直接使用 LINQ 的 ForEach() ,將自己寫的 MyForEach() 刪除

LINQ 如何實踐 ?

Refactoring to LINQ => ForEach

我們來看看 LINQ 的 source code 如何實踐 ForEach() ?

一開始對 action 進行判斷,若沒有則拋出 exception。

action 也搭配 for 進行,跟我們使用 foreach 類似。

基本上 LINQ 的 ForEach() 跟我們自己寫的 MyForEach() 差不多,但更加嚴謹

使用時機

ForEach() 表面上看起來是 foreach statement 的 syntax sugar,但事實上 ForEach() 在 FP 的意義並非如此。

FP 將 data 以 Dataflow 與 Pipeline 方式處理,因此提供了眾多 operator,而 operator 則必須搭配 pure function,不能有 Side Effect。但 Side effect 總要有人處理, ForEach() 就是讓你統一處理 Side Effect 之處。

與 Imperative 寫法的差異是 : Imperative 總是不斷的在處理 Side Effect,因此造成結果難以預測、難以測試,bug 就是由此展開;但 FP 對於 data 處理堅持採用 Dataflow 與 Pipeline,不使用 Side Effect,因此對 data 處理是可預測且容易測試,直到最後 data 處理完,才不得已使用 ForEach() 處理 Side Effect。

Console.WriteLine() 就是 I/O,就是 Side Effect,這是無法避免的,最後使用 ForEach() 統一解決。

Conclusion

  • . 與 Extension Method 是 C# 的 syntax sugar,其本質就是 FP 的 data 與 logic 分離與 Pipeline
  • Method Group 讓 FP 寫法會更為精簡,也是 C# 很重要的發明
  • FP 的 operator 主要在處理 Dataflow 與 Pipeline,應該使用 pure function,但 ForEach() 是少數讓你處理 Side Effect 的 operator,應該將 side effect 集中在 ForEach() 處理

Reference

ReactiveX , Functional Programming in JavaScript


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Pro Git

Pro Git

Scott Chacon / Apress / 2009-8-27 / USD 34.99

Git is the version control system developed by Linus Torvalds for Linux kernel development. It took the open source world by storm since its inception in 2005, and is used by small development shops a......一起来看看 《Pro Git》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具