zoukankan      html  css  js  c++  java
  • 站在C#和JS的角度细谈函数式编程与闭包

    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#的值类型和引用类型的区别。

    闭包跟匿名函数跟函数式编程之间有着细微关系。如果不好好理解,坑的只是自己。其实语言之间都是大同小异。

  • 相关阅读:
    SharePoint 2010 与RMS集成方案
    VBS基础教程
    c#中csc命令的用法
    文本文件编码格式转换
    “本地连接”属性中“身份验证”选项卡消失的处理方法
    如何组建中小型SharePoint服务器场
    PHP文件上传详解
    Flash AS3 RadioButton的使用方法
    SUSE ssh登录慢解决办法,ssh登录失败,但是strace一下就好了的分析查询 第一次ssh,路由的问题
    AS3组件之ComboBox下拉框
  • 原文地址:https://www.cnblogs.com/irenebbkiss/p/4651910.html
Copyright © 2011-2022 走看看