内容简介:LINQ 是 C# 3.0 實現 FP 重要里程碑,提供大量的 Operator,讓我們以 Pure Function 將 data 以 Dataflow 與 Pipeline 方式實現。本系列將先以 Imperative 實作,然後再重構成 FP,最後再重構成 LINQ Operator。本文將討論macOS High Sierra 10.13.6
LINQ 是 C# 3.0 實現 FP 重要里程碑,提供大量的 Operator,讓我們以 Pure Function 將 data 以 Dataflow 與 Pipeline 方式實現。本系列將先以 Imperative 實作,然後再重構成 FP,最後再重構成 LINQ Operator。
本文將討論 Select
Operator。
Version
macOS High Sierra 10.13.6
.NET Core 2.1
C# 7.2
Rider 2018.2.3
Imperative
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApp { class Program { static void Main(string[] args) { var data = Enumerable.Range(1, 3); var result = new List<int>(); foreach (var item in data) { result.Add(item * 2); } foreach (var item in result) { Console.WriteLine(item); } } } }
使用 Enumerable.Range()
產生 1, 2, 3
,建立暫存的 result
List,將所有元素都乘以 2
,最後使用 foreach()
印出每個值。
由於資料的改變,建立新的暫存 List 處理,是 Imperative 慣用手法。
Refactor to HOF
實務上這種建立新暫存 List 處理的作法,常常會遇到,若每次都使用 foreach
這種 statement 寫法,重複使用能力為 0,就每次都要不斷的寫 foreach
。
若我們能將這種 foreach
配合新暫存 List 處理做法,抽成 MyMap()
Higher Order Function,我們就能不斷 reuse MyMap()
,只要將不同的商業邏輯以 function 傳進 MyMap()
即可。
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApp { class Program { static void Main(string[] args) { var data = Enumerable.Range(1, 3); var result = MyMap(data, Double); foreach (var item in result) { Console.WriteLine(item); } int Double(int x) => x * 2; } private static IEnumerable<int> MyMap(IEnumerable<int> data, Func<int, int> func) { var result = new List<int>(); foreach (var item in data) { result.Add(func(item)); } return result; } } }
23 行
private static IEnumerable<int> MyMap(IEnumerable<int> data, Func<int, int> func) { var result = new List<int>(); foreach (var item in data) { result.Add(func(item)); } return result; }
自己以 MyMap()
實作出 foreach
statement + 暫存 List 處理的 Higher Order Function 版本。
第一個參數為 data,第二個參數為 function。
如此 MyMap()
function 就能被重複使用。
13 行
var result = MyMap(data, Double);
原來的 foreach()
statement 重構成 MyMap()
Higher Order Function,將 data 與 Double
Local Function 傳入即可。
Refactor to Yield Return
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApp { class Program { static void Main(string[] args) { var data = Enumerable.Range(1, 3); var result = MyMap(data, Double); foreach (var item in result) { Console.WriteLine(item); } int Double(int x) => x * 2; } private static IEnumerable<int> MyMap(IEnumerable<int> data, Func<int, int> func) { foreach (var item in data) { yield return func(item); } } } }
23 行
private static IEnumerable<int> MyMap(IEnumerable<int> data, Func<int, int> func) { foreach (var item in data) { yield return func(item); } }
但要建立暫存 List 會影響執行效率,也浪費記憶體,尤其暫存 List 只是中繼資料,並不是最後執行結果,因此改用 yield return
實現 Lazy Evaluation,直到真正需要結果時,才會執行 func(item)
,如此就不用建立暫存 List,繼能解省記憶體,又能增進執行效率。
Refactor to Generics
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApp { class Program { static void Main(string[] args) { var data = Enumerable.Range(1, 3); var result = MyMap(data, Double); foreach (var item in result) { Console.WriteLine(item); } int Double(int x) => x * 2; } private static IEnumerable<R> MyMap<T, R>(IEnumerable<T> data, Func<T, R> func) { foreach (var item in data) { yield return func(item); } } } }
23 行
private static IEnumerable<R> MyMap<T, R>(IEnumerable<T> data, Func<T, R> func) { foreach (var item in data) { yield return func(item); } }
事實上 MyMap()
不只適用於 int
,而且可適用於任何型別,因此重構成 <T, R>
。
Refactor to Extension Method
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApp { static class Program { static void Main(string[] args) { Enumerable .Range(1, 3) .MyMap(Double) .ToList() .ForEach(Console.WriteLine); int Double(int x) => x * 2; } private static IEnumerable<R> MyMap<T, R>(this IEnumerable<T> data, Func<T, R> func) { foreach (var item in data) { yield return func(item); } } } }
20 行
private static IEnumerable<R> MyMap<T, R>(this IEnumerable<T> data, Func<T, R> func) { foreach (var item in data) { yield return func(item); } }
MyMap()
需要兩個參數,使用上不是那麼方便,而且也無法 Pipeline 般使用,因此將第一個參數加上 this
,成為 Extension Method,
11 行
Enumerable .Range(1, 3) .MyMap(Double) .ToList() .ForEach(Console.WriteLine);
如此 MyMap()
就與 Range()
串起來了,而且也減少了一個參數。
Refactor to LINQ
using System; using System.Linq; namespace ConsoleApp { static class Program { static void Main(string[] args) { Enumerable .Range(1, 3) .Select(Double) .ToList() .ForEach(Console.WriteLine); int Double(int x) => x * 2; } } }
事實上 LINQ 早已提供 Select()
,不必我們自己實作,其功能完全等效於自己實作的 MyMap()
。
一般 FP 世界,將這種 operator 稱為 Map,如 ECMAScript 有 Array.prototype.map()
,F# 有 List.map()
,在 LINQ 則稱為 Select()
Refactor to Using Static
using static System.Linq.Enumerable; using static System.Console; namespace ConsoleApp { static class Program { static void Main(string[] args) { Range(1, 3) .Select(Double) .ToList() .ForEach(WriteLine); int Double(int x) => x * 2; } } }
使用 using static
之後,則 Range()
與 WriteLine()
可進一步縮短,更符合 FP 風格。
Conclusion
-
就算自己重構,也會重構出
Select()
Higher Order Function,只是因為太常使用,LINQ 已經內建Select()
- Yield return 可實現 Lazy Evaluation,繼可節省記憶體,又可增進執行效率
-
善用
using static
,可讓 class 的 static method 更像 function
Sample Code
完整的範例可以在我的 GitHub 上找到
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。