函數式編程中,一切皆爲函數,這個函數通常不是類級別的,其能夠保存在變量中,能夠當作參數或返回值,是函數級別的抽象和重用,將函數做爲可重用的基本模塊,就像面向對象中一切皆爲對象,把全部事物抽象爲類,面向對象編程經過繼承和組合來實現類或模塊重用,而函數式編程經過局部套用來實現函數重用;兩種編程模式相輔相成,各有側重點。函數式編程涉及高階函數,純函數、引用透明、閉包、局部套用、部分應用、惰性求值、單子等概念。git
C#不是函數式程序設計語言,可是隨着委託、lambda表達式、擴展方法、Linq、並行庫的引入,不斷方便咱們進行函數式程序設計,另外,Monads.net庫也方便咱們進行函數式編程。github
1、函數式編程基本概念算法
一、高階函數編程
以函數爲參數或返回結果的函數,如一個排序函數,其能適用於各類類型的數據,其排序邏輯同樣,可是不一樣數據類型的值比較方法不同,把比較函數當作參數,傳遞給排序函數。另外,C# 中Enumerable類中的Where、Select、SelectMany、First擴展方法都是高階函數。閉包
二、引用透明/純函數模塊化
一個函數返回值,只取決於傳遞給它的參數,程序狀態一般不會影響函數返回值,這樣的函數稱爲純函數,其沒有反作用,反作用即多個方法或函數共享訪問同一數據,函數式程序設計的主要思想之一就是控制這樣的反作用。函數式編程
三、變量不變性函數
變量分局部變量(方法或類實例的局部變量) 全局變量(類的靜態字段);變量是可變的,函數式程序設計並不歡迎程序中可變值的想法,變量值越公開帶來的問題越嚴重,通常原則是變量的值最好保持不變或在最小的做用域內保存其值,純函數最好只使用在本身模塊中定義的變量值,不訪問其做用域以外的任何變量。優化
四、閉包this
當函數能夠當成參數和返回值在函數之間傳遞時,編譯器利用閉包擴展變量的做用域,以保證隨時能獲得所須要數據;局部套用(currying或加里化)和部分應用依賴於閉包。
static Func<int, int> GetClosureFunction()
{
//局部變量
int val = 10;
//局部函數
Func<int, int> internalAdd = x => x + val;
Console.WriteLine(internalAdd(10));//輸出20
val = 30;
//局部變量的改變會影響局部函數的值,即便變量的改變在局部函數建立以後。
Console.WriteLine(internalAdd(10));//輸出40
return internalAdd;
}
static void Closoures()
{
Console.WriteLine(GetClosureFunction()(30));//輸出60
}
局部變量val 做用域應該只在GetClosureFunction函數中,局部函數引用了外層做用域的變量val,編譯器爲其建立一個匿名類,並把局部變量當成其中一個字段,並在GetClosureFunction函數中實例化它,變量的值保存在字段內,並在其做用域範圍外繼續使用。
五、局部套用或函數柯里化
函數柯里化是一種使用單參數函數來實現多參數函數的方法
多參函數:
Func<int, int, int> add = (x, y) => x + y;
單參數函數:
Func<int, Func<int, int>> curriedAdd = x => (y => x + y);
調用:curriedAdd (5)(3)
應用場景:預計算,記住前邊計算的值避免重複計算
static bool IsInListDumb<T>(IEnumerable<T> list, T item)
{
var hashSet = new HashSet<T>(list);
return hashSet.Contains(item);
}
調用:
IsInListDumb(strings, "aa");
IsInListDumb(strings, "aa");
改造後:
static Func<T, bool> CurriedIsInListDumb<T>(IEnumerable<T> list)
{
var hashSet = new HashSet<T>(list);
return item => hashSet.Contains(item);
}
調用:
var curriedIsInListDumb = CurriedIsInListDumb(strings);
curriedIsInListDumb("aa");
curriedIsInListDumb ("bb");
六、部分應用或偏函數應用
找一個函數,固定其中的幾個參數值,從而獲得一個新的函數;經過局部套用實現。
static void LogMsg(string range, string message)
{
Console.WriteLine($"{range} {message}");
}
//固化range參數
static Action<string> PartialLogMsg(Action<string, string> logMsg, string range)
{
return msg => logMsg(range, msg);
}
static void Main(string[] args)
{
PartialLogMsg(LogMsg, "Error")("充值失敗");
PartialLogMsg(LogMsg, "Warning")("金額錯誤");
}
部分應用例子:
代碼重複版本:
using(var trans = conn.BeginTransaction()){
ExecuteSql(trans, "insert into people(id, name)value(1, 'Harry')");
ExecuteSql(trans, "insert into people(id, name)value(2, 'Jane')");
...
trans.Commit();
}
優化1:函數級別模塊化
using(var trans = conn.BeginTransaction()){
Action<SqlCeTransaction, int, string> exec = (transaction, id, name) =>
ExecuteSql(transaction, String.Format(
"insert into people(id, name)value({0},'{1}'", id, name));
exec (trans, 1, 'Harry');
exec (trans, 2, 'Jane');
...
trans.Commit();
}
優化2:部分應用
using(var trans = conn.BeginTransaction()){
Func<SqlCeTransaction, Func<int, Action<string>> exec = transaction => id => name =>
ExecuteSql(transaction, String.Format(
"insert into people(id, name)value({0},'{1}'", id, name)))(trans);
exec (1)( 'Harry');
exec (2)( 'Jane');
...
trans.Commit();
}
優化3:直接經過閉包簡化
using(var trans = conn.BeginTransaction()){
Action<SqlCeTransaction, int, string> exec = ( id, name) =>
ExecuteSql(trans , String.Format(
"insert into people(id, name)value({0},'{1}'", id, name));
exec (1, 'Harry');
exec ( 2, 'Jane');
...
trans.Commit();
}
七、惰性求值/嚴格求值
表達式或表達式的一部分只有當真正須要它們的結果時纔對它們求值,嚴格求值指表達式在傳遞給函數以前求值,惰性求值的優勢是能夠提升程序執行效率,複雜算法中很難決定某些操做執行仍是不執行。
以下例子:
static int BigCalculation()
{
//big calculation
return 10;
}
static void DoSomething(int a, int b)
{
if(a != 0)
{
Console.WriteLine(b);
}
}
DoSomething(o, BigCalculation()) //嚴格求值
static HigherOrderDoSomething(Func<int> a, Func<int> b)
{
if(a() != 0)
{
Console.WriteLine(b());
}
}
HigherOrderDoSomething(() => 0, BigCalculation)//惰性求值
這也是函數式編程的一大好處。
八、單子(Monad)
把相關操做按某個特定類型連接起來。代碼更易閱讀,更簡潔,更清晰。
Monads.net是GitHub上一個開源的C#項目,提供了許多擴展方法,以便可以在C#編程時編寫函數式編程風格的代碼。主要針對class、Nullable、IEnuerable以及Events類型提供
一些擴展方法。地址:https://github.com/sergeyzwezdin/monads.net。下面舉些例子:
示例一: 使用With擴展方法獲取某人工做單位的電話號碼
var person = new Person();
var phoneNumber = "";
if(person != null && person.Work != null && person.Work.Phone != null)
{
phoneNumber = person.Work.Phone.Number;
}
在Monads.net中:
var person = new Person();
var phoneNumber = person.With(p => p.Work).With(w => w.Phone).With(p => p.Number);
代碼中主要使用了With擴展方法, 源代碼以下:
public static TResult With<TSource, TResult>(this TSource source, Func<TSource, TResult> action)
where TSource : class
{
if ((object) source != (object) default (TSource))
return action(source);
return default (TResult);
}
person.With(p => p.Work)這段代碼首先判斷person是否爲空,若是不爲Null則調用p => p.Work返回Work屬性,不然返回Null。
接下來With(w => w.Phone), 首先判斷上一個函數返回值是否爲Null,若是不爲Null則調用w => w.Phone返回Phone屬性,不然返回Null。
由此能夠看出, 在上面的With函數調用鏈上任何一個With函數的source參數是Null,則結果也爲Null, 這樣不拋出NullReferenceException。
示例二: 使用Return擴展方法獲取某人工做單位的電話號碼
在示例一中,若是person,Work,Phone對象中任一個爲Null值phoneNumber會被賦於Null值。若是在此場景中要求phoneNumber不能Null,而是設置一個默認值,應該怎麼辦?
var person = new Person();
var phoneNumber = person.With(p => p.Work).With(w => w.Phone).Return(p => p.Number, defaultValue:"11111111");
當調用Return方法的source參數爲Null時被返回。
示例三: Recover
Person person = null;
//person = new Person();
if(null == person)
{
person = new Person();
}
在Monads.net中:
示例四: try/catch
Person person = null;
try {
Console.WriteLine(person.Work);
} catch(NullReferenceException ex)
{
Console.WriteLine(ex.message);
}
在Monads.net中:
Person person = null;
person.TryDo(p => Console.WriteLine(p.Work), typeof(NullReferenceException)).Catch(ex => Console.WriteLine(ex.Message));
//忽略異常
Person person=null;
try {
Console.WriteLine(person.Work);
} catch()
{
}
在Monads.net中:
person.TryDo(p=>Console.WriteLine(p.Work)).Catch();
示例五: Dictionary.TryGetValue
var data = new Dictionary<int,string>();
string result = null;
if(data.TryGetValue(1, out result))
{
Console.WriteLine($"已找到Key爲1的結果:{result}");
}else
{
Console.WriteLine($"未找到Key爲1的結果");
}
在Monads.net中:
data.With(1).Return(_ => $"已找到Key爲1的結果:{_}", "未找到Key爲1的結果").Do(_ => Console.WriteLine(_));