精华内容
下载资源
问答
  • 为什么要去学习函数编程

    千次阅读 2020-12-31 14:58:53
    函数编程比较复杂比较枯燥,但是为了了解react和...为什么要去学习函数编程 函数编程其实相对于计算机的历史而言是一个非常古老的概念,甚至早已第一台计算机的诞生。他的演算并非设计在计算机上执行,而是在20世

    函数式编程比较复杂比较枯燥,但是为了了解react和redux,如果没有函数式编程的理论铺垫,很难学好他们。

    函数式编程在js当中是一个比较抽象的概念,大家在以前可能听说过函数式编程,但是可能并没有系统的去了解过他们。

    函数式编程和面向对象编程一样,是一套编程范式,你可以根据函数式编程的理论为你的代码设计这个过程。只不过但是函数式编程要求相对比较高一些

    为什么要去学习函数式编程

    函数式编程其实相对于计算机的历史而言是一个非常古老的概念,甚至早已第一台计算机的诞生。他的演算并非设计在计算机上执行,而是在20世纪三十年代引入的一套用于研究函数定义,函数应用和递归的形式系统。

    也就是说函数式编程已经是一个很老的概念了,那为什么我们还要学习他,其实函数式编程以前和前端没有任何关系,也并不流行。只是因为react和redux将它带火了。有了高阶函数,那么高阶函数就是函数式编程的一部分,所以才将函数式编程带火了。

    函数式编程主要是用于研究函数的定义,函数的应用和递归的而这样一个形式的系统。

    注意,函数式编程不是用函数来编程,也不是传统的面向过程编程,主旨在于将复杂的函数复合成简单的函数,运算过程尽量写成一系列嵌套的函数调用。大家注意区分用函数编程和函数式编程是不同的。

    react的高阶组件,使用了高阶函数来实现,高阶函数就是函数式编程的一个特性,我们后面会学到。虽然react当中使用了一些函数式编程的特性,但它并不是纯函数式的。

    另外react的一些生态,比如redux,它使用了函数式编程的一些思想,所以我们想要更好的学习react和redux的话,我们需要了解函数式编程。

    我们都知道vue3对vue2做了很大的重构,而且越来越偏向函数式,我们在使用vue3的一些api的时候可以感受到,在vue2的源码中也大量的时候到了高阶函数,这些流行框架都在趋向于函数式编程,我们甚至可以说你可以不学习这些框架,但是你不能不了解函数式编程。因为这些才是永远不变的内容。

    很多同学再学习js之前可能都了解过面向对象的语言,比如说Java,C#以及C++等等,所以在学习js的时候,我们也都是从面向对象开始学习的,我们会通过学习原型,原型链以及模拟实现继承的机制来实现面向对象的一些特性。我们在学习的过程中还会遇到this的各种各样问题,如果我们用到函数式编程的时候,我们就可以抛弃掉this。

    是用函数式编程有很多的好处,比如说打包的时候可 以更好的利用tree-shaking来过滤无用的代码。

    使用函数式编程还可以方便测试,方便并行处理,这些都是由函数式编程的特性来决定的。

    还有很多库可以帮助我们进行函数式开发,比如说lodash,underscore,ramda。

    这就是为什么要学习函数式编程。

    函数式编程的概念

    函数式编程是范畴轮的数学分支,是一门很复杂的数学,认为世界上所有的概念体系都可以抽象出一个范畴。范畴可以理解为群体的概念,比如一个班级中的同学,就可以理解为一个范畴。

    只要彼此之间存在某种关系概念,事物,对象等等,都构成范畴,任何事物只要找出他们之间的关系,就可以被定义。比如说教室中上课的人,可以彼此都不认识,但是大家的关系是同学,所以就是一个范畴。

    关系一般用箭头来表示,正式的名称叫做 态射 。范畴轮认为,同一个范畴的所有成员,就是不同状态的变形。通过态射一个成员就可以变形成另一个成员。简单来说就是每个成员之间都是有关系的。

    函数式编程英文的叫法是Functional Programming 缩写是FP。函数式编程是一种编程范式,我们可以认为他是一种编程的风格,他和面向对象是并列的关系。函数式编程我们可以认为是一种思维的模式,我们常听说的编程范式,还有面向过程变成和面向对象编程。

    函数式编程的思维方式,是把现实世界中的事物,和事物之间的联系,抽象到程序世界中。

    我们首先来解释一下程序的本质,就是根据输入然后根据某种运算获得相应的输出,程序在开发的过程中会涉及到很多说如和输出的函数,函数式编程就是对这些运算过程进行抽象。

    假设我们有个输入x,可以通过一个映射关系变成y,那这个映射关系就是函数式编程中的函数。

    关于函数式编程我们要注意的是,函数式编程中的函数,不是程序中的函数或者方法,不是说我们在编程过程中使用到了函数或者方法就是函数式编程,函数式编程中的函数,值得其实是数学中的函数,数学中的函数是用来描述映射关系的,例如 y = sin(x) 这个函数,sin是用来描述x和y的关系。当x=1时y的值也就确定了,也就是说当x的值确定了y的值一定也是确定的。

    在函数式编程中我们要求相同的输入始终要得到相同的输出,这是纯函数的概念。

    函数式编程就是对运算过程的抽象,下面我们用一段代码来体会一下函数式编程。

    比如我们要计算两个数的和,并且打印这个结果,我们一般会定义两个变量num1和num2,然后将这个两个变量想加,最后打印想加的结果。

    let num1 = 2;
    let num2 = 3;
    let num = num1 + num2;
    console.log(sum)
    

    那这是非函数式的,如果使用函数式的思想应该像下面这样,首先我们要对运算过程抽象add函数,这个函数接收两个参数n1和n2,当这个函数执行之后会把结果返回。也就是说,函数式编程中的函数一定要有输入和输出。

    function add (n1, n2) {
        return n1 + n2;
    }
    let sum = add(2, 3);
    console.log(sum);
    

    可以看到,当我们使用函数式编程的时候一定会有一些函数,这些函数后续可以无数次的重用,所以函数式编程的一个好处就是,可以让代码进行重用,而且在函数式编程的过程中,抽象出来的函数都是细粒度的,那这些函数将来可以重新去组合成功能更强大的函数。

    函数式编程不是说写几个函数就是函数式开发,他是用数学的思维方式借助js的语法来进行一些代码的开发,所以说函数式他是一套数学的规律。

    那这样他跟我们平常写代码有什么区别呢?用函数式编程的时候我们是不可以用if的,也没有else,因为数学中不存在if和else,也没有变量和while,整个都是数学的思维,然后用js的语法来承接。可以使用递归,因为递归是数学的概念。

    函数是一等公民

    所谓一等公民,指的是函数与其它数据类型一样,处于平等地位,可以赋值给其它变量,可以作为参数,也可以作为返回值。

    在函数式编程中,变量是不能被修改的,所有的变量只能被赋值一次,所有的值全都靠传参来解决。

    所以简单来说就是,函数是一等公民,可以赋值给变量,可以当做参数传递,可以作为返回值。

    在函数式编程中,只能用表达式,不能用语句,因为数学里面没有语句。

    因为变量只能被赋值一次,不能修改变量的值,所以不存在副作用,也不能修改状态。

    函数之间运行全靠参数传递,而且这个参数是不会被修改的,所以引用比较透明。

    高阶函数

    高阶函数的定义其实很简单,就是如果一个函数A可以接收另一个函数B作为参数,那么这种函数A就称之为高阶函数。说简单一点就是参数列表中包含函数。

    函数式编程的思想是对运算过程进行抽象,也就是把运算过程抽象成函数,然后在任何地方都可以去重用这些函数。

    抽象可以帮我们屏蔽实现的细节,我们以后在调用这些函数的时候只需要关注我们的目标就可以了,那高阶函数就是帮我们抽象这些通用的问题。

    我们举一个简单的例子,比如说我们想遍历打印数组中的每一个元素,如果我们使用面向过程编程代码如下。

    可以发现我们要写一个循环来做这样一件事,我们要关注数组的长度,要控制变量不能大于数组长度,要关心很多东西。

    // 面向过程方式
    let array = [1, 2, 3, 4];
    for (let i = 0; i < array.length; i++) {
        console.log(array[i]);
    }
    

    我们这里Array的forEach函数实现,我们在使用的时候不需要关注循环的具体实现,也就是不需要关注循环的细节,也不需要变量去控制,我们只需要知道forEach函数可以帮我们完成循环就ok了。

    // 高阶函数
    let array = [1, 2, 3, 4];
    array.forEach(item => {
        console.log(item);
    })
    

    这里的forEach就是对通用问题的一个抽象,我们可以看到使用forEach要比for循环简洁很多,所以我们使用函数式编程还有一个好处就是使代码更简洁。

    在js中,数组的forEach,map,filter,every,some,find, findIndex, reduce, sort等都是高阶函数,因为他们都可以接收一个函数为参数。

    闭包

    函数和其周围的状态的引用捆绑在一起,可以在另一个作用域中调用这个函数内部的函数并访问到该函数作用域中的成员。

    闭包的概念并不复杂,但是他的定义比较绕,我们通过一段代码来体会闭包的概念。

    首先我们定义一个makeFn的函数,在这个函数中定义一个变量msg,当这个函数调用之后,msg就会被释放掉。

    function makeFn () {
        let msg = 'Hello';
    }
    
    maknFn();
    

    如果我们在makeFn中返回一个函数,在这个函数中又访问了msg,那这就是闭包。

    和刚刚不一样的是,当我们调用完makeFn之后他会返回一个函数,接收的fn其实就是接收makeFn返回的函数,也就意味着外部的fn对函数内部的msg存在引用。

    所以当我们调用fn的时候,也就是调用了内部函数,会访问到msg,也就是makeFn中的变量。

    function makeFn () {
        let msg = 'Hello';
        return function() {
            console.log(msg);
        }
    }
    
    const fn = maknFn();
    
    fn();
    

    所以闭包就是在另一个作用域(这里是全局),可以调用到一个函数内部的函数(makeFn内部返回的函数),在这个函数中可以访问到这个函数(makeFn)作用域中的成员。

    根据上面的描述,闭包的核心作用就是把我们makeFn中内部成员的作用范围延长了,正常情况下makeFn执行完毕之后msg会被释放掉,但是这里因为外部还在继续引用msg,所以并没有被释放。

    我们接下来看下下面这个例子, 介绍一下闭包的作用。

    这里有一个once函数,他的作用就是控制fn函数只会执行一次,那如何控制fn只能执行一次呢?这里就需要有一个标记来记录,这个函数是否被执行了,我们这里定义一个局部变量done,默认情况下是false,也就是fn并没有被执行。

    在once函数内部返回了一个函数,在这个新返回的函数内部先去判断done,如果done为false,就把他标记为true,并且返回fn的调用。

    当once被执行的时候,我们创建一个done,并且返回一个函数。这个函数我们赋值给pay。

    当我们调用pay的时候,会访问到外部的done,判断done是否为false,如果是将done修改为true,并且执行fn。这样在下一次次调用pay的时候,由于done已经为true了,所以就不会再次执行了。

    function once(fn) {
        let done = false;
        return function() {
            if (!done) {
                done = true;
                return fn.apply(this, arguments);
            }
        }
    }
    
    let pay = once(function(money) {
        console.log(`${money}`);
    });
    
    // 只会执行一次。
    pay(1);
    pay(2);
    

    闭包的本质就是,函数在执行的时候会放到一个执行栈上执行,当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员。

    展开全文
  • 主要介绍了JDK都出到14了,你有什么理由不会函数编程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
  • 欢迎来到函数编程的世界。在这个只有函数的世界中,我们愉快地生活着,没有任何外部环境的依赖,没有状态,没有突变——永远没有。函数编程是最近的一个热点。你可能在团队中和小组会议中听说过这个术语,或许还...
    函数的第一条原则是要小。函数的第二条原则是要更小。
    

                                    —ROBERT C. MARTIN


           欢迎来到函数式编程的世界。在这个只有函数的世界中,我们愉快地生活着,没有任何外部环境的依赖,没有状态,没有突变——永远没有。函数式编程是最近的一个热点。你可能在团队中和小组会议中听说过这个术语,或许还做过一些思考。如果你已经了解了它的含义,非常好!但是那些不知道的人也不必担心。本章的目的就是:用通俗的语言为你介绍函数式编程。
            我们将以一个简单的问题开始本章:数学中的函数是什么?随后给出函数的定义并用其创建一个简单的JavaScript 函数示例。本章结尾将说明函数式编程带给开发者的好处。


    1.1 什么是函数式编程?为何它重要

        在开始了解函数式编程这个术语的含义之前,我们要回答另一个问题:数学中的函数是什么?数学中的函数可以写成如下形式:
    f(X) = Y
    这条语句可以被解读为“一个函数F,以X 作为参数,并返回输出Y。”例如,X 和Y 可以是任意的数字。这是一个非常简单的定义,但是其中包含了几个关键点:
    ● 函数必须总是接受一个参数。
    ● 函数必须总是返回一个值。
    ● 函数应该依据接收到的参数(例如X)而不是外部环境运行。
    ● 对于一个给定的X,只会输出唯一的一个Y。
           你可能想知道为什么我们要了解数学中的函数定义而不是JavaScript 中的。你是这样想的吗?对于我来说这是一个值得思考的问题。答案非常简单:函数式编程技术主要基于数学函数和它的思想。但是等等——我们并不是要在数学中教你函数式编程,而是使用JavaScript来传授该思想。但是贯穿全书,我们将看到数学函数的思想和用法,以便能够理解函数式编程。
            有了数学函数的定义,下面来看看JavaScript 函数的例子。假设我们要编写一个计税函数。在JavaScript 中你会如何做?


    注意

    本书的所有例子都用ES6 编写。书中的代码片段是独立的,所以你
    可以复制并把它们粘贴到任意喜欢的支持ES6 的浏览器中。所有的例子
    可以在Chrome 浏览器的51.0.2704.84 版本中运行。ES6 的规范请参见:
    http://www.ecma-international.org/ecma-262/6.0/。


    我们可以实现如代码清单1-1 所示的函数。

    代码清单1-1 用ES6 编写的计税函数
    var percentValue = 5;
    var calculateTax = (value) => { return value/100 * (100 + percentValue) }
    上面的calculateTax 函数准确地实现了我们的想法。你可以用参数调用该函数,它会在控制台中返回计算后的税值。该函数看上去很整洁,不是吗?让我们暂停一下,用数学的定义分析一下它。数学函数定义的关键是函数逻辑不应依赖外部环境。在calculateTax 函数中,我们让函数依赖全局变量percentValue。因此,该函数在数学意义上就不能被称为一个真正的函数。下面将修复该问题。请思考一下,为什么不能在模板中改变字体?

    修复方法非常简单:我们只需要移动percentValue,把它作为函数的参数。见代码清单1-2。

    代码清单1-2 重写计税函数
    var calculateTax = (value, percentValue) => { return value/100 * (100 +
    percentValue) }
    现在calculateTax 函数可以被称为一个真正的函数了。但是我们得到了什么?我们只是在其内部消除了对全局变量的访问。移除一个函数内部对全局变量的访问会使该函数的测试更容易(我们将在本章的稍后部分讨论函数式编程的好处)。
    现在我们了解了数学函数与JavaScript 函数的关系。通过这个简单的练习,我们就能用简单的技术术语定义函数式编程。函数式编程是一种范式,我们能够以此创建仅依赖输入就可以完成自身逻辑的函数。这保证了当函数被多次调用时仍然返回相同的结果。函数不会改变任何外部环境的变量,这将产生可缓存的、可测试的代码库。

    函数与JavaScript 方法
    前面介绍了很多有关“函数”的内容。在继续之前,我想确保你理解了函数和JavaScript 方法之间的区别。简言之,函数是一段可以通过其名称被调用的代码。它可以传递参数并返回值。
    然而,方法是一段必须通过其名称及其关联对象的名称被调用的代码。下面快速看一下函数和方法的例子,如代码清单1-3 和代码清单1-4 所示。

    函数
    代码清单1-3 一个简单的函数
    var simple = (a) => {return a} // 一个简单的函数simple(5) // 用其名称调用
    方法
    代码清单1-4 一个简单的方法
    var obj = {simple : (a) => {return a} }
    obj.simple(5) // 用其名称及其关联对象调用
    在函数式编程的定义中还有两个重要的特性并未提及。在研究函数式编程的好处之前,我们将在下一节详细阐述。


    1.2 引用透明性
    根据函数的定义,我们可以得出结论:所有的函数对于相同的输入都将返回相同的值。函数的这一属性被称为引用透明性(Referential Transparency)。下面举一个简单的例子,如代码清单1-5 所示:
    代码清单1-5 引用透明性的例子
    var identity = (i) => { return i }

    在上面的代码片段中,我们定义了一个简单的函数identity。无论传入什么作为输入,该函数都会把它返回。也就是说,如果你传入5,它就会返回5(换言之,该函数就像一面镜子或一个恒等式)。注意,我们的函数只根据传入的参数“i”进行操作,在函数内部没有全局引用(记住代码清单1-2,我们从全局访问中移除了“percentValue”并把它作为一个传入的参数)。该函数满足了引用透明性条件。现在假设该函数被用于其他函数调用之间,如下所示:
    sum(4,5) + identity(1)
    根据引用透明性的定义,我们可以把上面的语句转换为:
    sum(4,5) + 1
    该过程被称为替换模型(Substitution Model),因为你可以直接替换函数的结果(主要因为函数的逻辑不依赖其他全局变量),这与它的值是一样的。这使并发代码和缓存成为可能。根据该模型想象一下,你可以轻松地用多线程运行上面的代码,甚至不需要同步!为什么?同步的问题在于线程不应该在并发运行的时候依赖全局数据。遵循引用透明性的
    函数只能依赖来自参数的输入。因此,线程可以自由地运行,没有任何锁机制!
    由于函数会为给定的输入返回相同的值,实际上我们就可以缓存它了!例如,假设有一个函数“factorial”计算给定数值的阶乘。“factorial”接受输入作为参数以计算其阶乘。我们都知道“5”的“factorial”是“120”。
    如果用户第二次调用“5”的“factorial”,情况会如何呢?如果“factorial”函数遵循引用透明性,我们知道结果将依然是“120”(并且它只依赖输入参数)。记住这个特性后,就能够缓存“factorial”函数的值。因此,如果“factorial”以“5”作为输入被第二次调用,就能够返回已缓存的
    值,而不必再计算一次。
    在此可以看到,引用透明性在并发代码和可缓存代码中发挥着重要的作用。本章的稍后部分将编写一个用于缓存函数结果的库函数。

    引用透明性是一种哲学
    “引用透明性”一词来自分析哲学(https://en.wikipedia.org/wiki/Analytical_ philosophy)。该哲学分支研究自然语言的语义及其含义。单词“Referential”或“Referent”意指表达式引用的事物。句子中的上下文是“引用透明的”,如果用另一个引用相同实体的词语替换上下文中的一个词语,并不会改变句子的含义。
    这就是我们在本节定义的引用透明性。替换函数的值并不影响上下文。这就是函数式编程的哲学!

    1.3 命令式、声明式与抽象
    函数式编程主张声明式编程和编写抽象的代码。在更进一步介绍之前,我们需要理解这两个术语。我们都知道并使用过多种命令式范式。
    下面以一个问题为例,看看如何用命令式和声明式的方法解决它。
    假设有一个数组,你想遍历它并把它打印到控制台。代码如代码清
    单1-6 所示:
    代码清单1-6 用命令式方法遍历数组
    var array = [1,2,3]
    for(i=0;i<array.length;i++)
    console.log(array[i]) // 打印1, 2, 3
    这段代码运行良好。但是为了解决问题,我们精确地告诉程序应该“如何”做。例如,我们用数组长度的索引计算结果编写了一个隐式的for 循环并打印出数组项。在此暂停一下。我们的任务是什么?“打印数组的元素”,对不对?但是看起来我们像在告诉编译器该做什么。在本例中,我们在告诉编译器“获得数组长度,循环数组,用索引获取每个数组元素,等等。”我们将之称为“命令式”解决方案。命令式编程主张告诉编译器“如何”做。
    现在我们来看另一方面,声明式编程。在声明式编程中,我们要告诉编译器做“什么”,而不是“如何”做。“如何”做的部分将被抽象到普通函数中(这些函数被称为高阶函数,我们会在后续的章节中介绍)。现在我们可以用内置的forEach 函数遍历数组并打印它。见代码清单1-7。

    代码清单1-7 用声明式方法遍历数组
    var array = [1,2,3]
    array.forEach((element) => console.log(element))// 打印1, 2, 3
    上面的代码片段打印了与代码清单1-6 相同的输出。但是我们移除了“如何”做的部分,比如“获得数组长度,循环数组,用索引获取每一个数组元素,等等。”我们使用了一个处理“如何”做的抽象函数,如此可以让开发者只需要关心手头的问题(做“什么”的部分)。这非常棒!贯穿本书,我们都将创建这样的内置函数。
    函数式编程主张以抽象的方式创建函数,这些函数能够在代码的其他部分被重用。现在我们对什么是函数式编程有了透彻的理解。基于这一点,我们就能够去研究函数式编程的好处了。

    1.4 函数式编程的好处
    我们了解了函数式编程的定义和一个非常简单的JavaScript 函数。但是不得不回答一个简单的问题:“函数式编程的好处是什么?”这一节将帮助你透过现象看本质,了解函数式编程带给我们的巨大好处!大多数函数式编程的好处来自于编写纯函数。所以在此之前,我们将了解一下什么是纯函数。
    1.5 纯函数
    有了前面的定义,我们就能够定义纯函数的含义。纯函数是对给定的输入返回相同的输出的函数。举一个例子,见代码清单1-8:

    代码清单1-8 一个简单的纯函数
    var double = (value) => value * 2;
    上面的函数“double”是一个纯函数,只因为给它一个输入,它总是返回相同的输出。你不妨自己试试。用输入5 调用double 函数总是返回结果10!纯函数遵循引用透明性。因此,我们能够毫不犹豫地用10替换double(5)。
    所以,纯函数了不起的地方是什么?它能带给我们很多好处。下面依次讨论。
    1.5.1 纯函数产生可测试的代码
    不纯的函数具有副作用。下面以前面的计税函数为例进行说明(代
    码清单1-1):

    var percentValue = 5;
    var calculateTax = (value) => { return value/100 * (100 +
    percentValue) } //
    依赖外部环境的percentValue 变量
    函数calculateTax 不是纯函数,主要因为它依赖外部环境计算其逻
    辑。尽管该函数可以运行,但非常难于测试!下面看看原因。
    假设我们打算对calculateTax 函数运行测试,分别执行三次不同的
    税值计算。按如下方式设置环境:
    calculateTax(5) === 5.25
    calculateTax(6) === 6.3
    calculateTax(7) === 7.3500000000000005
    整个测试通过了!但是别急,既然原始的calculateTax 函数依赖外
    部环境变量percentValue,就有可能出错。假设你在运行相同的测试用
    例时,外部环境也正在改变变量percentValue:
    calculateTax(5) === 5.25
    // percentValue 被其他函数改成2
    calculateTax(6) === 6.3 // 这条测试能通过吗?
    // percentValue 被其他函数改成0
    calculateTax(7) === 7.3500000000000005 // 这条测试能通过吗,还是
    会抛出异常?
    如你所见,此时的calculateTax 函数很难测试。但是我们可以很容
    易地修复这个问题,从该函数中移除外部环境依赖,代码如下:
    var calculateTax = (value, percentValue) => { return value/100 *
    (100 +percentValue) }
    现在可以顺畅地测试calculateTax 函数了!在结束本节前,我们需
    要提及纯函数的一个重要属性,即“纯函数不应改变任何外部环境的
    变量。”
    换言之,纯函数不应依赖任何外部变量(就像例子中展示的那样),
    也不应改变任何外部变量。我们通过改变任意一个外部变量就能马上理
    解其中的含义。例如,考虑代码清单1-9:

    代码清单1-9 badFunction 例子
    var global = "globalValue"
    var badFunction = (value) => { global = "changed"; return value * 2 }
    当badFunction 函数被调用时,它将全局变量global 的值改成
    changed。需要担心这件事吗?是的!假设另一个函数的逻辑依赖global
    变量!因此,调用badFunction 就影响了其他函数的行为。具有这种性
    质的函数(也就是具有副作用的函数)会使代码库变得难以测试。除了测
    试,在调试的时候这些副作用会使系统的行为变得非常难以预测!
    至此,我们通过简单的示例了解到纯函数有助于我们更容易地测试
    代码。现在来看一下纯函数的其他好处——合理的代码。
    1.5.2 合理的代码
    作为开发者,我们应该善于推理代码或函数。通过创建和使用纯函
    数,能够非常简单地实现该目标。为了明确这一点,我们将使用一个简
    单的double 函数(来自代码清单1-8):
    var double = (value) => value * 2
    通过函数的名称能够轻易地推理出:这个函数把给定的数值加倍,
    其他什么也没做!事实上,根据引用透明性概念,我们可以简单地用相
    应的结果替换double 函数调用!开发者的大部分时间花在阅读他人的代
    码上。在代码库中包含具有副作用的函数对团队中的其他开发者来说是
    难以阅读的。包含纯函数的代码库会易于阅读、理解和测试。记住,函
    数(无论它是否为纯函数)必须总是具有一个有意义的名称。按照这种说
    法,在给定行为后你不能将函数“double”命名为“dd”。
    小脑力游戏
    我们只需要用值替换函数,就好像不看它的实现就知道结果一样!
    这是你在理解函数思想过程中的一个巨大进步。我们取代函数值,就好
    像这是它要返回的结果!
    为了快速练习一下你的脑力,下面用内置的Math.max 函数测试一下你的推理能力。
    给定函数调用:
    Math.max(3,4,5,6)
    结果是什么?
    为了给出结果,你看了max 的实现了吗?没有,对不对?为什么?
    答案是Math.max 是纯函数。现在喝一杯咖啡吧,你已经完成了一项伟
    大的工作!
    1.6 并发代码
    纯函数总是允许我们并发地执行代码。因为纯函数不会改变它的环
    境,这意味着我们根本不需要担心同步问题!当然,JavaScript 并没有
    真正的多线程用来并发地执行函数, 但是如果你的项目使用了
    WebWorker 来并发地执行多任务,该怎么办呢?或者有一段Node 环境
    中的服务端代码需要并发地执行函数,又该怎么办呢?
    例如,假设我们有代码清单1-10 给出的如下代码:
    代码清单1-10 非纯函数
    let global = "something"
    let function1 = (input) => {
    // 处理input
    // 改变global
    global = "somethingElse"
    }
    let function2 = () => {
    if(global === "something")
    {
    // 业务逻辑
    }
    }
    如果我们需要并发地执行function1 和function2,该怎么办呢?假
    设线程一(T-1)选择function1 执行,线程二(T-2)选择function2 执行。现在两个线程都准备好执行了,那么问题来了。如果T-1 在T-2 之前执行,
    情况会如何?由于两个函数(function1 和function2)都依赖全局变量
    global,并发地执行这些函数就会引起不良的影响。现在把这些函数改
    为纯函数,如代码清单1-11 所示:
    代码清单1-11 纯函数
    let function1 = (input,global) => {
    // 处理input
    // 改变global
    global = "somethingElse"
    }
    let function2 = (global) => {
    if(global === "something")
    {
    // 业务逻辑
    }
    }
    此处我们移动了global 变量,把它作为两个函数的参数,使它们变
    成纯函数。现在可以并发地执行这两个函数了,不会带来任何问题。由
    于函数不依赖外部环境(global 变量),因此我们不必再像代码清单1-10
    那样担心线程的执行顺序。
    本节说明了纯函数是如何使代码并发执行的,你不必担心任何问题。
    1.7 可缓存
    既然纯函数总是为给定的输入返回相同的输出,那么我们就能够缓
    存函数的输出。讲得更具体些,请看下面的例子。假设有一个做耗时计
    算的函数,名为longRunningFunction:
    var longRunningFunction = (ip) => { //do long running tasks and return }
    如果longRunningFunction 函数是纯函数,我们知道对于给定的输
    入,它总会返回相同的输出!考虑到这一点,为什么要通过多次的输入
    来反复调用该函数呢?不能用函数的上一个结果代替函数调用吗?

    (此处再次注意我们是如何使用引用透明性概念的,因此,用上一个结
    果值代替函数不会改变上下文)。假设我们有一个记账对象,它存储了
    longRunningFunction 函数的所有调用结果,如下所示:
    var longRunningFnBookKeeper = { 2 : 3, 4 : 5 . . . }
    longRunningFnBookKeeper 是一个简单的JavaScript 对象,存储了所
    有的输入(key)和输出(value),它是longRunningFunction 函数的调用结
    果。现在使用纯函数的定义,我们能够在调用原始函数之前检查key 是
    否在longRunningFnBookKeeper 中,如代码清单1-12 所示:
    代码清单1-12 通过纯函数缓存结果
    var longRunningFnBookKeeper = { 2 : 3, 4 : 5 }
    // 检查key 是否在longRunningFnBookKeeper 中
    // 如果在,则返回结果,否则更新记账对象
    longRunningFnBookKeeper.hasOwnProperty(ip) ?
    longRunningFnBookKeeper[ip] :
    longRunningFnBookKeeper[ip] = longRunningFunction(ip)
    上面的代码相当直观。在调用真正的函数之前,我们用相应的ip
    检查函数的结果是否在记账对象中。如果在,则返回之,否则就调用原
    始函数并更新记账对象中的结果。看到了吗?用更少的代码很容易使函
    数调用可缓存。这就是纯函数的魅力!
    在本书后面,我们将编写一个使用纯函数调用的用于处理缓存或技
    术性记忆(technical memorization)的函数库。
    1.8 管道与组合
    使用纯函数,我们只需要在函数中做一件事。纯函数能够自我理解,
    通过其名称就能知道它所做的事情。纯函数应该被设计为只做一件事。
    只做一件事并把它做到完美是UNIX 的哲学,我们在实现纯函数时也将
    遵循这一原则。UNIX 和LINUX 平台有很多用于日常任务的命令。例
    如,cat 用于打印文件内容,grep 用于搜索文件,wc 用于计算行数等。
    这些命令的确一次只解决一个问题。但是我们可以用组合或管道来完成复杂的任务。假如我们要在一个文件中找到一个特定的名称并统计它的
    出现次数。在命令提示符中要如何做?命令如下:
    cat jsBook | grep –i "composing" | wc
    上面的命令通过组合多个函数解决了我们的问题。组合不是
    UNIX/LINUX 命令行独有的,但它们是函数式编程范式的核心。我们把
    它们称为函数式组合(Functional Composition)。假设同样的命令行在
    JavaScript 函数中已经实现了,我们就能够根据同样的原则使用它们来
    解决问题!
    现在考虑用一种不同的方式解决另一个问题。你想计算文本中的行
    数。如何解决呢?你已经有了答案。不是吗?
    根据我们的定义,命令实际上是一种纯函数。它接受参数并向调用
    者返回输出,不改变任何外部环境!
    注意
    也许你在想,JavaScript 支持用于组合函数的操作符“|”吗?答案
    是否定的,但是我们可以创建一个。后面的章节将创建相应的函数。
    遵循一个简单的定义,我们收获了很多好处。在结束本章之前,我
    想说明纯函数与数学函数之间的关系。
    1.9 纯函数是数学函数
    在1.7 节“可缓存”中我们见过如下一段代码(代码清单1-12):
    var longRunningFunction = (ip) => { // 执行长时间运行的任务并返回
    var longRunningFnBookKeeper = { 2 : 3, 4 : 5 }
    // 检查key 是否在longRunningFnBookKeeper 中
    // 如果在,则返回结果,否则更新记账对象
    longRunningFnBookKeeper.hasOwnProperty(ip) ?
    longRunningFnBookKeeper[ip] :
    longRunningFnBookKeeper[ip] = longRunningFunction(ip)

    这段代码的主要目的是缓存函数调用。我们通过记账对象实现了该
    功能。假设我们多次调用了longRunningFunction,longRunningFnBook-
    Keeper 增长为如下的对象:
    longRunningFnBookKeeper = {
    1 : 32,
    2 : 4,
    3 : 5,
    5 : 6,
    8 : 9,
    9 : 10,
    10 : 23,
    11 : 44
    }
    现在假设longRunningFunction 的输入范围限制为1-11 的整数(正如
    例子所示)。由于我们已经为这个特别的范围构建了记账对象,因此只
    能参照longRunningFnBookKeeper 来为给定的输入返回输出。
    下面分析一下该记账对象。该对象为我们清晰地描绘出,函数
    longRunningFunction 接受一个输入并为给定的范围(在这个例子中,是
    1-11)映射输出。此处的关键是,输入(在这个例子中,是key)具有强制
    的、相应的输出(在这个例子中,是结果)。在key 中也不存在映射两个
    输出的输入。
    通过上面的分析,我们再看一下数学函数的定义(这次是来自维
    基百科的更具体的定义,网址为https://en.wikipedia.org/wiki/Function_
    (mathematics)):
    在数学中,函数是一种输入集合和可允许的输出集合之间的关系,
    具有如下属性:每个输入都精确地关联一个输出。函数的输入称为参数,
    输出称为值。对于一个给定的函数,所有被允许的输入集合称为该函数
    的定义域,而被允许的输出集合称为值域。
    上面的定义与纯函数完全一致!看一下longRunningFnBookKeeper
    对象。你能找到函数的定义域和值域吗?当然可以!通过这个非常简单
    的例子,很容易看到数学函数的思想被借鉴到函数式范式的世界(正如
    本章开始阐述的那样)。

    1.10 我们要构建什么
    本章介绍了很多关于函数和函数式编程的知识。有了这些基础知
    识,我们将构建一个名为ES6-Functional 的函数式库。这个库将在全书
    中逐章地构建。通过构建这个函数式库,你将探索如何使用JavaScript
    函数,以及如何在日常工作中应用函数式编程(使用创建的函数解决代
    码库中的问题)!
    1.11 JavaScript 是函数式编程语言吗
    在结束本章之前,我们要回答一个基础的问题。JavaScript 是函数
    式编程语言吗?答案不置可否。在本章的开头,我们说函数式编程主张
    函数必须接受至少一个参数并返回一个值。不过坦率地讲,我们可以创
    建一个不接受参数并且实际上什么也不返回的函数。例如,下面的代码
    在JavaScript 引擎中是一段有效的代码:
    var useless = () => {}
    上面的代码在JavaScript 中执行时不会报错!原因是JavaScript 不
    是一种纯函数语言(比如Haskell),而更像是一种多范式语言。但是如本
    章所讨论的,这门语言非常适合函数式编程范式。到目前为止,我们讨
    论的技术和好处都可以应用于纯JavaScript!这就是书名的由来!
    JavaScript 语言支持将函数作为参数,以及将函数传递给另一函数
    等特性——主要原因是JavaScript 将函数视为一等公民(我们将在后续章
    节做更多的讨论)。由于函数定义的约束,开发者需要在创建JavaScript
    函数时将其考虑在内。如此,我们就能从函数式编程中获得很多优势,
    正如本章中讨论的一样。

    1.12 小结
    在本章中,我们介绍了在数学和编程世界中函数的定义。我们从数
    学函数的简单定义开始,研究了短小而透彻的函数例子和JavaScript 中
    的函数式编程。还定义了什么是纯函数并详细讨论了它们的益处。在本
    章结尾,我们说明了纯函数和数学函数之间的关系。还讨论了JavaScript
    如何被视为一门函数式编程语言。通过本章的学习,你将收获颇丰。
    在下一章中,我们将学习用ES6 创建并执行函数。用ES6 创建函
    数有多种方式,我们将在下一章中学习这些方式!

    展开全文
  • 越来越多的主流语言在设计的时候几乎无一例外都会参考**函数式特性**( `lambda` 表达式,原生支持 `map,reduce...`),就连面向对象语言的 `Java8` 也慢慢开始支持函数编程,所以再不学习函数编程可能就晚了!

    在编程世界中向来就没有一家独大的编程风格,至少目前还是百家争鸣的春秋战国,除了众所周知的面向对象编程还有日渐流行的函数式编程,当然这也是本系列文章的重点.

    越来越多的主流语言在设计的时候几乎无一例外都会参考函数式特性( lambda 表达式,原生支持 map,reduce...),就连面向对象语言的 Java8 也慢慢开始支持函数式编程,所以再不学习函数式编程可能就晚了!

    go-functional-programming-about-function.jpg

    但是在正式学习函数式编程之前,不妨和早已熟悉的面向对象编程心底里做下对比,通过对比学习的方式,相信你一定会收获满满,因此特地整理出来关于 Go 语言的面向对象系列文章,邀君共赏.

    上述系列文章讲解了 Go 语言面向对象相关知识点,如果点击后没有自动跳转,可以关注微信公众号「雪之梦技术驿站」查看历史文章,再次感谢你的阅读与关注.

    生物学家和数学家的立场不同

    虽然是同一个世界,但是不同的人站在各自立场看问题,结果自然会千人千面,各有不同.

    生物学家会下意识对动植物进行分类归纳,面向对象编程也是如此,用一系列的抽象模型去模拟现实世界的行为规律.

    go-functional-programming-about-biology.jpg

    数学家向来以严谨求学著称,作为最重要的基础科学,数学规律以及归纳演绎方法论对应的就是函数式编程,不是模拟现实而是描述规律更有可能创造规律.

    go-functional-programming-about-math.jpg

    标准的函数式编程具有浓厚的数学色彩,幸运的是,Go 并不是函数式语言,所以也不必受限于近乎苛责般的条条框框.

    简单来说,函数式编程具有以下特点:

    • 不可变性: 不用状态变量和可变对象
    • 函数只能有一个参数
    • 纯函数没有副作用

    go-functional-programming-about-feature.jpg

    摘自维基百科中关于函数式编程中有这么一段话:

    In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

    上述的英文的大致意思是说:函数式编程将计算机程序看成是数学函数的推演,不用状态变量也不用可变对象来表达数与数之间的关系.

    如需了解详情,可点击访问维基百科关于函数式编程 Functional programming 的相关介绍.

    函数式编程的立足点和出发点是函数,复杂函数是基本函数经过一定组合规律形成的,所以描述复杂函数的过程就是如何拆解重组的过程.

    所以接下来我们一边复习一边学习函数的基本特点,为接下来理解函数式编程打下基础,关于函数的基础语言可参考 go 学习笔记之值得特别关注的基础语法有哪些

    函数的基础语法和高级特性

    下面以最基本四则运算为例,贯穿全文讲解函数的基本语法和高级特性,力求做到知其然知其所以然.

    • func 定义普通函数

    eval 函数定义了加减乘除基本运算规则,若不支持操作类型则抛出异常,终止程序.

    func eval(a, b int, op string) int {
        var result int
        switch op {
        case "+":
            result = a + b
        case "-":
            result = a - b
        case "*":
            result = a * b
        case "/":
            result = a / b
        default:
            panic("unsupported operator: " + op)
        }
        return result
    }
    

    测试未定义操作取余 % 运算时,则抛出异常,unsupported operator: % ,说明仅仅支持加减乘除基本运算.

    func TestEval(t *testing.T) {
        // 3 -1 2 0 unsupported operator: %
        t.Log(
            eval(1, 2, "+"),
            eval(1, 2, "-"),
            eval(1, 2, "*"),
            eval(1, 2, "/"),
            eval(1, 2, "%"),
        )
    }
    
    • 多返回值定义标准函数

    Go 语言和其他主流的编程语言明显不同的是,函数支持多返回值,通常第一个返回值表示真正结果,第二个返回值表示是否错误,这也是 Go 关于异常错误设计的独特之处.

    如果正常返回,则表示没有错误,那么第一个返回值是正常结果而第二个返回值则是空 nil;如果异常返回,第一个返回值设计无意义的特殊值,第二个返回值是具体的错误信息,一般非 nil.

    func evalWithStandardStyle(a, b int, op string) (int, error) {
        switch op {
        case "+":
            return a + b, nil
        case "-":
            return a - b, nil
        case "*":
            return a * b, nil
        case "/":
            return a / b, nil
        default:
            return 0, fmt.Errorf("unsupported operator: %s", op)
        }
    }
    

    改造 eval 函数以编写真正 Go 程序,此时再次测试,结果显示遇到没有定义的操作符时不再抛出异常而是返回默认零值以及给出简短的错误描述信息.

    func TestEvalWithStandardStyle(t *testing.T) {
        // Success: 2
        if result, err := evalWithStandardStyle(5, 2, "/"); err != nil {
            t.Log("Error:", err)
        } else {
            t.Log("Success:", result)
        }
    
        // Error: unsupported operator: %
        if result, err := evalWithStandardStyle(5, 2, "%"); err != nil {
            t.Log("Error:", err)
        } else {
            t.Log("Success:", result)
        }
    }
    
    • 其他函数作为参数传入

    上例通过多返回值解决了遇到不支持的运算符会报错终止程序的问题,但是并没有真正解决问题,假如真的想要进行非预定义的运算时,同样是无能为力!

    谁让你只是使用者而不是设计者呢!

    那么舞台交给你,你就是主角,你想要怎么处理输入怎么输出就怎么处理,全部逻辑转移给使用者,这样就不存在无法满足需求的情况了.

    func evalWithApplyStyle(a, b int, op func(int, int) (int, error)) (int, error) {
        return op(a, b)
    }
    

    操作符由原来的字符串 string 更改成函数 func(int, int) (int, error),舞台交给你,全靠自由发挥!

    evalWithApplyStyle 函数内部直接调用函数参数 op 并返回该函数的处理结果,当前演示示例中函数的控制权完全转移给函数入参 op 函数,实际情况可按照实际需求决定如何处理 evalWithApplyStyle 逻辑.

    func divide(a, b int) (int, error) {
        return a / b, nil
    }
    
    func mod(a, b int) (int, error) {
        return a % b, nil
    }
    

    自己动手,丰衣足食,顺手定义除法 divide 和取余 mod 运算,接下来测试下实现效果.

    func TestEvalWithApplyStyle(t *testing.T) {
        // Success: 2
        if result, err := evalWithApplyStyle(5, 2, divide); err != nil {
            t.Log("Error:", err)
        } else {
            t.Log("Success:", result)
        }
    
        // Success: 1
        if result, err := evalWithApplyStyle(5, 2, mod); err != nil {
            t.Log("Error:", err)
        } else {
            t.Log("Success:", result)
        }
    }
    

    测试结果很理想,不仅实现了减加乘除等基本运算,还可以实现之前一直没法实现的取余运算!

    这说明了这种函数作为参数的做法充分调动劳动人民积极性,妈妈再也不用担心我无法实现复杂功能了呢!

    • 匿名函数也可以作为参数

    一般而言,调用函数时都是直接用函数名进行调用,单独的函数具有可复用性,但如果本就是一次性函数的话,其实是没必要定义带函数名形式的函数.

    依然是上述例子,这一次对两个数的运算规则不再是数学运算了,这一次我们来比较两个数的最大值,使用匿名函数的形式进行实现.

    func TestEvalWithApplyStyle(t *testing.T) {
        // Success: 5
        if result, err := evalWithApplyStyle(5, 2, func(a int, b int) (result int, e error) {
            if a > b {
                return a, nil
            }
            return b, nil
        }); err != nil {
            t.Log("Error:", err)
        } else {
            t.Log("Success:", result)
        }
    }
    
    • 函数的返回值可以是函数

    依然是上述示例,如果由于原因不需要立即返回函数的计算结果而是等待使用者自己觉得时机合适的时候再计算返回值,这时候函数返回值依然是函数就很有作用了,也就是所谓的惰性求值.

    func evalWithFunctionalStyle(a, b int, op func(int, int) (int, error)) func() (int, error) {
        return func() (int, error) {
            return op(a, b)
        }
    }
    

    上述函数看起来可能有点难以理解,实际上相对于上例仅仅更改了返回值,由原来的 (int, error) 更改成 func() (int, error) ,其余均保持不变哟!

    evalWithFunctionalStyle 函数依然是使用者的主场,和上例相比的唯一不同之处在于,你的主场你做主,什么时候裁判完全自己说了算,并不是运行后就立马宣布结果.

    func pow(a, b int) (int, error) {
        return int(math.Pow(float64(a), float64(b))),nil
    }
    
    func TestEvalWithFunctionalStyle(t *testing.T) {
        ef := evalWithFunctionalStyle(5, 2, pow)
    
        time.Sleep(time.Second * 1)
    
        // Success: 25
        if result, err := ef(); err != nil {
            t.Log("Error:", err)
        } else {
            t.Log("Success:", result)
        }
    }
    

    time.Sleep(time.Second * 1) 演示代码代表执行 evalWithFunctionalStyle 函数后可以不立即计算最终结果,等待时机合适后由使用者再次调用 ef() 函数进行惰性求值.

    // 1 1 2 3 5 8 13 21 34 55
    //     a b
    //       a b
    func fibonacci() func() int {
        a, b := 0, 1
        return func() int {
            a, b = b, a+b
            return a
        }
    }
    
    • 函数可以充当类型

    上述示例中讲解了函数可以作为返回值,参数有函数,返回值也有参数,所以 evalWithFunctionalStyle 函数看起来比较费劲,而 Go 语言的类型别名就是为了简化而生的,更何况函数是 Go 中的一等公民,当然也适合了.

    func evalWithFunctionalStyle(a, b int, op func(int, int) (int, error)) func() (int, error) {
        return func() (int, error) {
            return op(a, b)
        }
    }
    

    于是打算把入参函数 func(int, int) (int, error) 和返回值函数 func() (int, error) 进行统一,而入参函数和返回值函数唯一不同之处就是入参个数不同,所以顺理成章想到了 Go 函数中的不定长参数相关语法.

    type generateIntFunc func(base ...int) (int, error)
    

    这样入参函数和出参函数都可以用 generateIntFunc 类型函数进行替代,接着改造 evalWithFunctionalStyle 函数.

    func evalWithObjectiveStyle(a, b int, op generateIntFunc) generateIntFunc {
        return func(base ...int) (i int, e error) {
            return op(a, b)
        }
    }
    

    改造后的 evalWithObjectiveStyle 函数看起来比较简洁,花花架子中看是否中用还不好说,还是用测试用例说话吧!

    func TestEvalWithObjectiveStyle(t *testing.T) {
        ef := evalWithObjectiveStyle(5, 2, func(base ...int) (int,error) {
            result := 0
            for i := range base {
                result += base[i]
            }
            return result,nil
        })
    
        time.Sleep(time.Second * 1)
    
        // Success: 7
        if result, err := ef(); err != nil {
            t.Log("Error:", err)
        } else {
            t.Log("Success:", result)
        }
    }
    

    函数别名进行类型化后并不影响功能,依然是函数式编程,不过夹杂了些面向对象的味道.

    • 类型化函数可以实现接口

    函数通过别名形式进行类型化后可以实现接口,某些程度上可以视为一种类型,因此实现接口也是顺理成章的事情.

    func (g generateIntFunc) String() string {
        r,_ := g()
        return fmt.Sprint(r)
    }
    

    此处示例代码中为类型化函数 generateIntFunc 实现 String 接口方法,可能并没有太大实际意义,仅仅是为了讲解这个知识点而硬凑上去的,实际情况肯定会有所不同.

    func TestEvalWithInterfaceStyle(t *testing.T) {
        ef := evalWithObjectiveStyle(5, 2, func(base ...int) (int,error) {
            result := 0
            for i := range base {
                result += base[i]
            }
            return result,nil
        })
    
        time.Sleep(time.Second * 1)
    
        // String: 7
        t.Log("String:", ef.String())
    
        // Success: 7
        if result, err := ef(); err != nil {
            t.Log("Error:", err)
        } else {
            t.Log("Success:", result)
        }
    }
    

    惰性求值获取的函数变量 ef 此时可以调用 String 方法,也就是具备对象化能力,得到的最终结果竟然和直接运行该函数的值一样?

    有点神奇,目前还不理解这是什么操作,如果有 Go 语言的大佬们不吝赐教的话,小弟感激不尽!

    • 水到渠成的闭包

    函数的参数,返回值都可以是另外的函数,函数也可以作为引用那样传递给变量,也存在匿名函数等简化形式,除此之外,类型化后的函数还可以用来实现接口等等特性应该足以阐释一等公民的高贵身份地位了吧?

    如此强大的函数特性,只要稍加组合使用就会拥有强大的能力,并且 Go 语言并不是严格的函数式语言,没有太多语法层面的限制.

    // 1 1 2 3 5 8 13 21 34 55
    //     a b
    //       a b
    func fibonacci() func() int {
        a, b := 0, 1
        return func() int {
            a, b = b, a+b
            return a
        }
    }
    

    斐波那契数列函数 fibonacci 的返回值是真正的生成器函数,每次调用都会生成新的斐波那契数字.

    这就是 Go 语言实现闭包的一种简单示例,fibonacci 函数本身的变量 a,b 被内部匿名函数 func() int 所引用,而这种引用最终被使用者不断调用就会导致最初的 a,b 变量一直被占用着,只要继续调用这种生成器,裴波那契数列的数字就会一直递增.

    // 1 1 2 3 5 8 13 21 34 55
    func TestFibonacci(t *testing.T) {
        f := fibonacci()
        for i := 0; i < 10; i++ {
            fmt.Print(f(), " ")
        }
        fmt.Println()
    }
    
    func TestFibonacci(t *testing.T) {
        f := fibonacci()
        for i := 0; i < 10; i++ {
            fmt.Print(f(), " ")
        }
        fmt.Println()
    }
    

    go-functional-programming-about-fib.png

    函数式编程入门函数总结

    • 函数是一等公民,其中函数参数,变量,函数返回值都可以是函数.
    • 高阶函数是普通函数组合而成,参数和返回值可以是另外的函数.
    • 函数是函数式编程的基础,支持函数式编程但并不是函数式语言.
    • 没有纯粹函数式编程的条条框框,更加灵活自由,良好的可读性.

    如果你觉得本文对你有所帮助,请随手点个赞再走呗或者关注下公众号「雪之梦技术驿站」定期更新优质文章哟!

    雪之梦技术驿站.png

    展开全文
  • 两天看书看到了函数编程那节,感觉到了挺多东西,之前看别人代码都写的挺漂亮,现在我写的代码是有点乱,所以还是要学习下别人的编程模式
  • 文章目录函数编程高阶函数(Higher-order function)map/reducefilter用filter求素数exercisesorted-排序算法exercise返回函数函数作为返回值闭包exercise匿名函数erercise小结装饰器定义使用示例exercise小结偏...
  • Python学习十一:函数编程

    千次阅读 2015-06-13 08:48:55
    这也是我第一接触函数编程这个概念,并不知道是干嘛的?好奇心驱使下学习了一下,有了大致的了解: 函数编程自己的理解:就跟说话一样写程序,这个程序写出来可以直白的告诉人是要干嘛的。 以下是我读到的关于...
    这也是我第一接触函数式编程这个概念,并不知道是干嘛的?好奇心驱使下学习了一下,有了大致的了解:
    函数式编程自己的理解:就跟说话一样写程序,这个程序写出来可以直白的告诉人是要干嘛的。
    以下是我读到的关于函数式编程的文章的描述:
    

    函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数![1]

    函数式编程的准则:不依赖于外部的数据,而且也不改变外部数据的值,而是返回一个新的值给你。[2]

    函数式编程的理念:把函数当成变量来用,关注于描述问题而不是怎么实现,这样可以让代码更易读。[2]

    函数式编程到底有什么好处,为什么会变得越来越流行?[3]
    1. 代码简洁,开发快速;
    2. 接近自然语言,易于理解;
    3. 更方便的代码管理;
    4. 易于”并发编程”

    参考文章链接:
    [1]、函数式编程
    [2]、函数式编程
    [3]、函数式编程初探

    以下是我遇见的几个不错的个人站点,他们都很厉害:
    1、http://www.liaoxuefeng.com/
    这个网站可以学到:Python、Git
    2、http://coolshell.cn
    这个网站可以学到很多,比如……
    3、http://www.ruanyifeng.com/home.html
    这个网站可以学到,哈哈——写诗的

    都是不错的!

    展开全文
  • Scala函数编程

    2017-05-26 10:22:26
    函数编程(FP)是一种...《Scala函数编程》是针对希望学习FP并将它应用于日常编码中的程序员而写的,内容包括:函数编程的概念;函数编程相关的各种“为什么”和“怎么做”;如何编写多核程序;练习和检测。
  • React学习之 -- 函数编程

    千次阅读 2017-04-24 15:56:45
    React把需要不断重复构建的UI...)函数编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且**避免使用程序状态以及易变对象**。函数
  • 函数编程及实例

    2019-08-11 02:45:09
    函数编程有许多优势,由此越来越受欢迎。然而每个编程范式 (paradigm) 都有自己唯一的术语,函数编程也不例外。我们提供一张术语表,希望使你学习函数编程变得容易些。
  • KotlinKatas Kotlin培训资料库用于通过仅使用Arrow进行纯函数编程来解决一些常见的katas,从而学习Kotlin和函数编程。 选项卡列表:#Kata Stateme KotlinKatas Kotlin培训资料库用于通过仅使用Arrow进行纯函数...
  • Python学习9:函数编程

    万次阅读 2014-01-14 16:22:35
    1. 什么是函数编程?  函数编程使用一系列的函数解决问题。函数仅接受输入并产生输出,不包含任何能影响输出的内部状态。任何情况下,使用 相同的参数调用函数始终能产生同样的结果。  在一个函数式的...
  • 函数编程FP

    千次阅读 2020-10-25 14:21:43
    函数编程(Functional Programming, FP)是编程范式之一,我们常听说的编程范式还有面向过程编程、面向对象编程。我们在分别使用这三种编程范式进行思考和编码时,思维方式和编码结果都会非常的不同,如下案例: // ...
  • 12.7_命令式编程vs函数编程函数编程__匿名函数、高阶函数、装饰器|Python3.8入门_&_进阶_&_原生爬虫实战
  • 一个超棒可视化学习函数编程的游戏
  • 趣味函数编程圣经

    2020-08-19 01:46:33
    主要介绍了函数编程的的相关资料,有趣的讲解了函数编程的相关知识,帮助大家更好的理解学习,感兴趣的朋友可以了解下
  • 什么是函数编程? 函数编程是一种面向函数函数组合的编程方式。  什么是函数?从数学的角度,函数即Function,是从集合A到集合B的一种映射关系。如果集合A中的每一个元素都对应到集合B中的某一个元素,那么...
  • Java8新引入函数编程方式,大大的提高了编码效率。本文将对涉及的对象等进行统一的学习及记录。 首先需要清楚一个概念:函数式接口;它指的是有且只有一个未实现的方法的接口,一般通过FunctionalInterface这个...
  • 本书内容全面,示例丰富,适合想要了解函数编程的JavaScript程序员和学习JavaScript的函数式程序员阅读。 第1章 JavaScript函数编程简介1 1.1 JavaScript案例1 1.2 开始函数编程4 1.2.1 为什么函数编程很...
  • javaScript函数编程

    2016-12-20 13:48:46
    本书内容全面,示例丰富,适合想要了解函数编程的JavaScript程序员和学习JavaScript的函数式程序员阅读。 作者简介 · · · · · · Michael Fogus是Dynamic Animation Systems的软件架构师,在分布式仿真、机器...
  • 这是一系列关于函数编程的文章,主要记录我在函数编程学习方面的心得体会,部分参考于部分书籍或者文章,纯粹个人学习,不做任何商业用途,如有冒犯请及时指正。 es6 every函数 every函数我们在日常开发中可能...
  • 函数编程思想概论

    千次阅读 2019-06-26 10:57:16
    函数编程思想概论前言函数λ 演算λ项绑定变量和自由变量约简α 变换β 约简η 变换纯函数、副作用和引用透明性函数编程与并发编程总结 原文地址 前言 在讨论函数编程(Functional Programming)的具体内容...
  • Scala函数编程.pdf

    2019-05-18 21:06:04
    函数编程(FP)是一种...《Scala函数编程》是针对希望学习FP并将它应用于日常编码中的程序员而写的,内容包括:函数编程的概念;函数编程相关的各种“为什么”和“怎么做”;如何编写多核程序;练习和检测。
  • 函数编程

    千次阅读 2017-09-20 14:13:40
    "函数编程", 又称泛函编程, 是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。它的基础是 λ 演算(lambda calculus)。λ演算可以接受函数当作输入(参数)和输出(返回值)。
  • 第9章 从面向对象到函数编程 假如本书的写作时间倒退回十年前,书名可能会变成JavaScript面向对象编程思想。自上世纪90年代兴起的面向对象编程思想随Java的繁荣达于顶点,在JavaScript从一门只被用来编写零星的...
  • JAVA函数编程

    千次阅读 2019-02-17 22:40:55
    JAVA函数编程背景常见的编程范式函数编程的优劣JAVA8中为函数编程引入的变化JAVA函数编程可以简单概括基本函数Lambda表达式方法引用Stream流API创建操作中间操作终止从操作并行流级联表达式与柯里化收集器...
  • JavaScript函数编程

    千次阅读 2016-12-03 15:58:03
    JavaScript函数编程摘要以往经常看到”函数编程“这一名词,却始终没有花时间去学习,暑期实习结束之后一直忙于边养老边减肥,81天成功瘦身30斤+ ,开始回归正常的学习生活。 便在看《JavaScript函数编程》这...
  • 《JavaScriptES6函数编程入门经典》使用JavaScriptES6带你学习函数编程。你将学习柯里化、偏函数、高阶函数以及Monad等概念。  目前,编程语言已经将焦点从对象转移到函数。JavaScript支持函数编程,并允许...
  • 函数编程,这个词语由两个名词构成,函数编程编程这个词我就不用解释了,大家都是做这个的。函数,其实单独抽离出来这个词语,也并不陌生,那二者组合后的到底是什么呢,下面这篇文章主要给大家介绍了关于Java...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 659,809
精华内容 263,923
关键字:

不会函数怎么学编程