標籤:
1.函數式編程是什嗎?
摘自百度的說法是。函數式編程是種編程典範,它將電腦運算視為函數的計算。函數程式設計語言最重要的基礎是 λ 演算(lambda calculus)。而且λ演算的函數可以接受函數當作輸入(參數)和輸出(傳回值)。和指令式編程相比,函數式編程強調函數的計算比指令的執行重要。和過程化編程相比,函數式編程裡,函數的計算可隨時調用。
舉個例子。平時如果我們要對集合中的元素進行篩選的話,每次都要寫迴圈,都寫這麼一大堆代碼,麻煩不?
static void Main(string[] args) { List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; List<int> result1 = new List<int>(); List<int> result2 = new List<int>(); foreach (var item in list) { if (item % 2 == 0) result1.Add(item); } foreach (var item in list) { if (item > 3) result2.Add(item); } }
有沒辦法構造一個通用的方法?這個方法內部去迴圈我們要篩選的數組。這樣調用方只要指定篩選條件即可。
我們仔細觀察下,歸納下它們的共同只處。
1.需要迴圈
這個很簡單,想到迴圈,我們就應該想到IEnumable
2.篩選條件
我們能不能把篩選元素的這個條件看成一個函數?如
public bool Function(object 當前元素){ //條件 if(當前元素.xx屬性>2)//滿足條件 return true; else return false;}
看到這個方法是不是會想到Func<object,bool>?
3.返回結果
這個可以看成把滿足條件的元素全部加入到一個集合中,返回回去
知道了這3個條件後,我們大概能構造一個這樣的東西
static void Main(string[] args) { List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; var result = MyWhere(list, x => x % 2 == 0); } public static IEnumerable<TInput> MyWhere<TInput>(IEnumerable<TInput> inputlist, Func<TInput, bool> func) { foreach (TInput item in inputlist) { if (func(item)) yield return item; } yield break; }
但我們每次都要傳入一個list,感覺好麻煩,是不是能改造成擴充方法?
class Program { static void Main(string[] args) { List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; var result = list.MyWhere(x => x % 2 == 0); } } public static class MyLinq { public static IEnumerable<TInput> MyWhere<TInput>(this IEnumerable<TInput> inputlist, Func<TInput, bool> func) { foreach (TInput item in inputlist) { if (func(item)) yield return item; } yield break; } }
現在是不是爽多了?
我歸納的函數式編程的核心思想是將相同邏輯的部分封裝起來。不同邏輯的部分將它封裝成一個函數,由調用方來指定這個函數的邏輯
很多人想問了,JAVA為什麼沒有類似LINQ這類的擴充方法。。
因為JAVA沒有委託這東西,它的思想是完全的面向介面。當然你也可以弄個類似LINQ這類的方法,但用起來會非常變態。首先你要實現定義的某個介面,然後再實現介面裡定義的Where方法,這個方法裡寫你的實現(篩選)邏輯。。這誰會用啊?
如果C#沒有匿名委託或lambda。。類似LINQ這類的擴充方法用起來也會很不友好,可以說是變態。
你想想 x=>x.age>5 一句話能搞定,硬要你寫個方法。。public bool Find(對象 x){ return x.age>5; }
2.LINQ能移植到JS上嗎?
答案是肯定的。可以!函數式編程只是一種思想,只要語言支援匿名方法,玩起來都很爽。
JS是支援匿名函數的,而且是弱類型,所以玩起來非常爽了。
一開始我就在想,有沒linq to javascript這東西。結果一搜,果然有=。=
(function () { LINQ = function (data) { if (!(data instanceof Array) && !(data instanceof $.fn.init)) throw "只支援數組或JQUERY對象"; this.data = data; }; LINQ.prototype = { Where: function (func) { var result = new Array(); for (var i = 0; i < this.data.length; i++) { var item = this.data[i]; if (func(item)) result.push(item); } this.data = result; return this; }, Foreach: function (func) { for (var i = 0; i < this.data.length; i++) { var item = this.data[i]; func(item); } } } })(); $(function () { var linq = new LINQ([1, 2, 3, 4, 5, 6]); var result = linq.Where(function (item) { return item >= 4; }).Foreach(function (item) { console.log(item); }); })
3.JS的閉包是什嗎?C#有閉包這個概念嗎?
摘自百度的說法是。閉包是可以包含自由(未綁定到特定對象)變數的代碼塊;這些變數不是在這個代碼塊內或者任何全域上下文中定義的,而是在定義代碼塊的環境中定義(局部變數)。
這樣說大家可能不明白,下面來看看代碼。
情境1
javscript
$(function () { var array = []; for (var i = 0; i < 10; i++) { array.push(function () { console.log(i); }); } array.forEach(function (item) { item(); }); })
C#
List<Action> list = new List<Action>(); for (int i = 0; i < 10; i++) { list.Add(() => { Console.WriteLine(i); }); } list.ForEach(x => x());
我們函數體內部對函數體外部的變數i產生了依賴。當我們調用Console.WriteLine(i)時,i已經是9了。而且9個委託中依賴的都是同1個執行個體i。所以列印出來的全部是10。
如果想輸出1,2,3,4,5....該怎麼改?其實很簡單。
情境2
javascript
$(function () { var array = []; for (var i = 0; i < 10; i++) { var func = function (item) { array.push(function () { console.log(item); }); }; func(i); } array.forEach(function (item) { item(); }); })
C#
List<Action> list = new List<Action>(); for (int i = 0; i < 10; i++) { Action<int> temp = (val) => { list.Add(() => { Console.WriteLine(val); }); }; temp(i); } list.ForEach(x => x());
其實當我們執行temp函數的時候。val已經對i進行拷貝了,val跟i已經沒半毛錢關係了(因為C#對於實值型別的拷貝是深度拷貝(deep coln,參考型別是拷貝引用),val是一個完全新的執行個體。所以輸出的結果可想而知了。1,2,3,4,5....9。如果覺得難理解可以看下面這段。
static List<Action> list = new List<Action>(); static unsafe void Main(string[] args) { for (int i = 0; i < 10; i++) { Show(i); } list.ForEach(x => x()); } public static unsafe void Show(int val) { list.Add(() => { Console.WriteLine(val); }); }
2段代碼是等效的。只不過這裡把匿名方法換成了有方法名的具體方法。這樣看應該就很好理解了吧。
情境3
上面2種情況下,JS和C#執行後的結果完全一樣。但是請看下面這種情況。
javascript
$(function () { var array = []; for (var i = 0; i < 10; i++) { var temp = i; array.push(function () { console.log(temp); }); } array.forEach(function (item) { item(); }); })
C#
List<Action> list = new List<Action>(); for (int i = 0; i < 10; i++) { var temp = i; list.Add(() => { Console.WriteLine(temp); }); } list.ForEach((x) => x());
C#理所當然的輸出1,2,3,4,5,6...9 為什嗎?上面說過實值型別拷貝是深度拷貝。所以這裡會有9個temp執行個體。而Console.WriteLine(temp)裡的temp分別依賴這9個執行個體的。
再看看JS。執行後,神奇的事情發生了。全部是9。當初我心裡很糾結,我實在是想不明白為什麼。
後來終於找到答案了。因為JS在函數裡面沒有代碼塊範圍(原諒我JS真的很菜)。temp表面上是放在for裡面,其實是放在for外面。好吧,心裡舒坦多了。如果大家還是不明白,請看下面代碼。
static unsafe void Main(string[] args) { List<Action> list = new List<Action>(); var temp = 0; for (int i = 0; i < 10; i++) { temp = i; list.Add(() => { Console.WriteLine(temp); }); } list.ForEach((x) => x()); }
我們list裡的所有委託中的Console.WriteLine(temp)依賴的都是同一個temp執行個體。
如果還是不懂。。。。請再看下面這2段代碼
public class student { public string name { get; set; } } static unsafe void Main(string[] args) { List<Action> list = new List<Action>(); student stud = null; for (int i = 0; i < 10; i++) { stud = new student() { name = "學生" + i }; list.Add(() => { Console.WriteLine(stud.name); }); } list.ForEach((x) => x()); }
執行結果
public class student { public string name { get; set; } } static unsafe void Main(string[] args) { List<Action> list = new List<Action>(); for (int i = 0; i < 10; i++) { student stud = new student() { name = "學生" + i }; list.Add(() => { Console.WriteLine(stud.name); }); } list.ForEach((x) => x()); }
執行結果
首先最重要的是要理解好C#的實值型別和參考型別的區別。
閉包跟匿名函數跟函數式編程之間有著細微關係。如果不好好理解,坑的只是自己。其實語言之間都是大同小異。
(轉) 站在C#和JS的角度細談函數式編程與閉包