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

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

时间: 2024-10-07 10:10:54

站在C#和JS的角度细谈函数式编程与闭包的相关文章

(转) 站在C#和JS的角度细谈函数式编程与闭包

1.函数式编程是什么? 摘自百度的说法是.函数式编程是种编程典范,它将电脑运算视为函数的计算.函数编程语言最重要的基础是 λ 演算(lambda calculus).而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值).和指令式编程相比,函数式编程强调函数的计算比指令的执行重要.和过程化编程相比,函数式编程里,函数的计算可随时调用. 举个例子.平时如果我们要对集合中的元素进行筛选的话,每次都要写循环,都写这么一大堆代码,麻烦不? static void Main(string[] arg

如何编写高质量的 JS 函数(3) --函数式编程[理论篇]

本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/EWSqZuujHIRyx8Eb2SSidQ作者:杨昆 [编写高质量函数系列]中, <如何编写高质量的 JS 函数(1) -- 敲山震虎篇>介绍了函数的执行机制,此篇将会从函数的命名.注释和鲁棒性方面,阐述如何通过 JavaScript 编写高质量的函数. <如何编写高质量的 JS 函数(2)-- 命名/注释/鲁棒篇>从函数的命名.注释和鲁棒性方面,阐述如何通过 JavaScri

如何编写高质量的 JS 函数(4) --函数式编程[实战篇]

本文首发于 vivo互联网技术 微信公众号? 链接:https://mp.weixin.qq.com/s/ZoXYbjuezOWgNyJKmSQmTw 作者:杨昆 ?[编写高质量函数系列],往期精彩内容: <如何编写高质量的 JS 函数(1) -- 敲山震虎篇>介绍了函数的执行机制,此篇将会从函数的命名.注释和鲁棒性方面,阐述如何通过 JavaScript 编写高质量的函数. ?<如何编写高质量的 JS 函数(2)-- 命名/注释/鲁棒篇>从函数的命名.注释和鲁棒性方面,阐述如何通

Node.js学习笔记【1】入门(服务器JS、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块)

笔记来自<Node入门>@2011 Manuel Kiessling JavaScript与Node.js Node.js事实上既是一个运行时环境,同时又是一个库. 使用Node.js时,我们不仅仅在实现一个应用,同时还实现了整个HTTP服务器. 一个基础的HTTP服务器 server.js:一个可以工作的HTTP服务器 var http = require("http"); http.createServer(function(request, response) { r

细谈Spring(一)spring简介

Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架.  然而,Spring的用途不仅限于服务器端的开发.从简单性.可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益. Spring的核心是个轻量级容器(container),实现了IoC(Inversion of Control)模式的容器,Spring的目标是实现一个全方位的整合框架,在Spr

细谈微信提示已停止访问网页的解决方案,微信跳转链接的生成

相信大部分试用微信分享转发链接的时候,都很容易碰到链接在微信中无法打开的问题.通常这种情况微信会给个提示 “已停止访问该网址” ,导致这个情况的因素有以下三点. 1. 分享链接被多人举报. 2. 链接含违规内容,含敏感词. 3. 被腾讯检测系统判断为诱导分享内容. 被人举报我们无法阻止,因为现在行业竞争大,举报的人里其实用户占很少数,多数都是来自同行的恶意举报,这是没有办法的事.第一种情况我们无法避免,但是我们可以避免链接因含敏感词或者含违规内容从而被腾讯主动拦截. 那么有朋友问到底怎么避免呢?

js 函数式编程 浅谈

js 函数式编程 函数式的思想, 就是不断地用已有函数, 来组合出新的函数. 函数式编程具有五个鲜明的特点: 1. 函数是"第一等公民" 指的是函数与其他数据类型一样,处于平等地位 2. 只用"表达式",不用"语句" "表达式"(expression)是一个单纯的运算过程,总是有返回值: "语句"(statement)是执行某种操作,没有返回值. 3. 没有"副作用" 指的是函数内部与外

[原创译书] JS函数式编程 2.3 函数式程序员的工具集

?? Functional Programming in Javascript 主目录第二章 函数式编程基础上一节 与函数共舞 函数式程序员的工具集 如果你仔细看了到目前为止出现过的示例代码,你会发现这里面的一些方法不太熟悉. 它们是map().filter()和reduce()函数,它们对任何语言的函数式编程都至关重要. 它们可以让你不必使用循环和语句,写出更简洁的代码. map().filter()和reduce()函数组成了函数式程序员工具集的核心部分,这个工具集包括一系列纯的. 高阶的函

[原创译书] JS函数式编程 前言

前言 函数式编程是一种能够让你编写更聪明的代码的方式,可以减低复杂度,增强模块化. 它是一种通过灵巧地变化.组合.使用函数达到编写简洁代码的方式. Javascript提供了一个实现这些的超赞的途径.Javascript,这个Internet的脚本语言, 它的核心实际上是一个函数式语言.通过学习如何显露出它作为一个函数式语言的真实身份, 我们可以实现强大的.更易维护的以及更可靠的web应用. 通过这些,Javascript的那些怪癖和缺陷将会立刻变得清晰,并且语言本身也将会无限精彩. 学习如何使