站在C#和JS的角度細談函數式編程與閉包

來源:互聯網
上載者:User

標籤:

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的角度細談函數式編程與閉包

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.