内容简介: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 上找到
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
写给大忙人看的Java SE 8
【美】Cay S. Horstmann(凯.S.霍斯曼) 编 / 张若飞 / 电子工业出版社 / 2014-11 / 59.00元
《写给大忙人看的Java SE 8》向Java开发人员言简意赅地介绍了Java 8 的许多新特性(以及Java 7 中许多未被关注的特性),《写给大忙人看的Java SE 8》延续了《快学Scala》“不废话”的风格。 《写给大忙人看的Java SE 8》共分为9章。第1章讲述了lambda表达式的全部语法;第2章给出了流的完整概述;第3章给出了使用lambda表达式设计库的有效技巧;第4章......一起来看看 《写给大忙人看的Java SE 8》 这本书的介绍吧!