函数式编程简介-附入门方法

WHAT? 什么是函数式编程?

函数式编程是一种编程范式。

编程范式又是什么?

编程范式是一种解决问题的思路。

我们熟悉的命令式编程把程序看作一系列改变状态的指令;而函数式编程把程序看作一系列数学函数映射的组合

编程范式和编程语言无关,任何编程语言都可以按照函数式的思维来组织代码。

i++; // 命令式 关心指令步骤
[i].map(x => x + 1); // 函数式 关心映射关系

WHY? 函数式有什么好处?

  • 易写易读 聚焦重要逻辑,摆脱例如循环之类的底层工作
  • 易复用 面向对象可复用的单位是类,函数式可复用的是函数,更小更灵活
  • 易测 纯函数【后面会讲】不依赖外部环境,测试起来准备工作少
  • 看起来很厉害 被人夸奖能增强信心和动力,所以这点也很重要

HOW? 如何做起?

方法不难,回学校念个博士,搞清楚范畴论,幺半群之类的就可以了。

人生苦短,还是来点实际的吧。

  1. filter map reduce 三板斧用好,从循环中解放出来
  2. small pure function 多写小的纯函数,小指功能聚焦
  3. compose pipeline curry 三个工具利用好,把小函数像搭积木一样拼成大函数

filter map reduce 三板斧

来个例子:找出集合中的素数,算出它们平方的和。

独孤九剑之命令式

const isPrimeNumber = x => {
    if (x <= 1) return false;

    let testRangStart = 2,
        testRangeEnd = Math.floor(Math.sqrt(x));

    let i = testRangStart;
    while (i <= testRangeEnd) {
        if (x % i == 0) return false;
        i++;
    }

    return true;
};

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

let sum = 0;

for (let i = 0; i < arr.length; i++) {
    if (isPrimeNumber(arr[i])) {
        sum += arr[i] * arr[i];
    }
}

console.log(sum);

破——剑——嗯....函数式

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const sum = arr.filter(isPrimeNumber)
    .map(x => x * x)
    .reduce((acc, cur) => acc + cur, 0);

console.log(sum);

看吧,for循环没了,代码意图也更明显了。

  1. filter(isPrimeNumber) 找出素数
  2. map(x => x * x) 变成平方
  3. reduce((acc, cur) => acc + cur, 0) 求和

是不是比命令式看着更清晰了?

isPrimeNumber的函数式写法也放出,去掉了循环,看看好懂不。

// 输入范围,获得一个数组,例如 输入 1和5,返回 [1, 2, 3, 4, 5]
const range = (start, end) => start <= end ? [start].concat(range(start + 1, end)) : [];
const isPrimeNumber = x =>
    x >= 2 ? range(2, Math.floor(Math.sqrt(x))).every(cur => x % cur != 0) : false;

有人说函数式的效率不高,因为filter map reduce每次调用,内部都会遍历一遍集合,而命令式只遍历了一次。

函数式是更高级的抽象,主要声明解决问题的步骤,把性能优化交给框架或者runtime来解决。

  • 框架

    transducer 可以让集合只遍历一次【篇幅有限,这里不展开】

    memorize 记录已经算过的,提高效率【后面讲纯函数的时候,会给出实现】

  • runtime

    有的语言map是多线程运行的,函数式代码不变,runtime一优化,性能就大幅的提升了,而前面的命令式,就做不到这一点。

small pure function

纯函数有两点要求:

  1. 相同的传参,返回值一定相同
  2. 函数调用不会对外界造成影响,如不会修改外部对象

看个例子

let name = ‘apolis‘;
const greet = () => console.log(‘Hello ‘ + name);

greet();
name = ‘kzhang‘;
greet();

greet函数依赖外部变量name,相同的传参【都不传参也算相同的传参】屏幕输出的内容却不一样,所以它不纯,鉴定完毕。

const greet = name => console.log(‘Hello ‘ + name);

这样就好多了,不受外部变量的影响了。

不过更严格的认为,调用这个函数造影响了控制台console,所以还不算纯。

const greet = name => ‘Hello ‘ + name;

这样才够纯,同时greet也摆脱了对控制台的依赖,可以适用的范围更广了。

我们要学会把纯的留给自己,把不纯的甩给别人......咳咳,关在函数外面。

由于它的纯,同样的传参,返回值一定相同。

我们可以把算过的结果保存下来,下次调用传的参数发现算过了,直接返回之前计算的结果,提升效率。

const memorize = fn => {
    let cache = {};
    return x => {
        if (cache.hasOwnProperty(x)) return cache[x];
        else {
            const result = fn(x);
            cache[x] = result;
            return result;
        }
    }
};

利用上面的工具函数,我们可以缓存纯函数的计算结果,三板斧的例子filter改一下就可以了。

const sum = arr.filter(memorize(isPrimeNumber))
    .map(x => x * x)
    .reduce((acc, cur) => acc + cur, 0);

console.log(sum);

如果数组中包含重复元素,这样就能减少计算次数了。

命令式写法要达到这个效果,改动就大的多了。

compose pipeline curry

写了一堆small pure function,怎么把他们组合成更强大的功能呢?

compose pipeline curry这三位该出场了。

compose

举个例子。

const upperCase = str => str.toUpperCase();
const exclaim = str => str + ‘!‘;
const holify = str => ‘Holy ‘ + str;

现在需要一个amaze方法,字符串前面添加Holy,后面添加叹号,全部转为大写。

const amaze = str => upperCase(exclaim(holify(str)));

很不优雅对不对?

看看compose怎么帮我们解决这个问题。

const compose = (...fns) => x => fns.reduceRight((acc, cur) => cur(acc), x);
const amaze = compose(upperCase, exclaim, holify)
console.log(amaze(‘functional programing‘));

这里用到了reduceRight,和reduce的区别就是数组是从后往前遍历的。

compose内的函数是从右往左运行的,也就是先holifyexclaimupperCase

有人可能看不惯从右往左运行,于是又有了一个pipeline

pipeline

compose的区别就是换个方向,compose用的是reduceRightpipeline用的是reduce

const pipeline = (...fns) => x => fns.reduce((acc, cur) => cur(acc), x);
const amaze = pipeline(holify, exclaim, upperCase)
console.log(amaze(‘functional programing‘));

curry

上面compose pipeline里的函数参数都只是一个,如果函数要传多个参数怎么办?

解决办法就是用curry【柯里化】,把函数变成一个参数的。

const add = (x, y) => x + y;
const multiply = (x, y) => x * y;

这两个函数都是需要传两个参数的,现在我需要一个函数,把数字先加5再乘2。

const add5ThenMultiplyBy2 = x => multiply(add(x, 5), 2)

很不好看,我们来curry一下再compose看看。

怎么curry

把括号去掉,逗号变箭头就可以了。

这样传入一个参数x的时候,返回了一个新函数,等待着接收参数y

const add = x => y => x + y;
const multiply = x => y => x * y;

接下来,我们又可以用compose

const add5ThenMultiplyBy2 = x => compose(multiply(2), add(5));

不过curry之后的add方法要这么调用了

add(2)(3)

原先的调用方式add(2, 3)都得改掉了。不喜欢这个副作用?再奉上一个工具函数curry

const curry = fn => {
    const inner = (...args) => {
        if (args.length >= fn.length) return fn(...args);
        else return (...newArgs) => inner(...args, ...newArgs);
    }
    return inner;
};

传入fn返回一个新函数,新函数调用时判断传入的参数个数有没有达到fn的要求,达到了,直接返回fn调用的结果;没达到,继续返回一个新新函数,记录着之前已传入的参数。

const add = (x, y) => x + y;
const curriedAdd = curry(add);

这样两种调用方式都支持了。

curriedAdd(2)(3);
curriedAdd(2, 3);

总结

函数式是一种编程思维,声明式、更抽象。

这种思维方式的利弊,大型项目里怎么用,我还没深刻的体会,练习还不足。

建议新手和我一样从下面三点开始多写多思考。

  1. filter map reduce 三板斧用好,从循环中解放出来
  2. small pure function 多写小的纯函数,小指功能聚焦
  3. compose pipeline curry 三个工具利用好,把小函数像搭积木一样拼成大函数

后面我会继续学习functor monad相关的知识,感兴趣可以关注。

原文地址:https://www.cnblogs.com/apolis/p/9370847.html

时间: 2024-11-02 02:44:58

函数式编程简介-附入门方法的相关文章

Python的函数式编程,从入门到?放弃?

转:http://python.jobbole.com/84927/ 很早以前就听说过了函数式编程,印象中是一种很晦涩难懂的编程模式,但却一直没有去进行了解. 恰好这周组内的周会轮到我主持,一时也没想到要分享什么.灵光一闪,就选定函数式编程这个主题吧,反正组里的同事都没有学过,只需要讲解入门方面的知识就好,也正好可以借这个机会逼迫自己去学习下这种新的编程方式. 经过初步了解,发现支持函数式编程的语言挺多的,除了像Lisp.Scheme.Haskell.Erlang这样专用的函数式编程语言,我们常

Python 进阶(一)函数式编程简介

来自慕课网: 简介: 函数:function ,在入门课程已学 函数式:functional,一种编程范式 函数式编程是一种抽象计算的编程模式,函数≠函数式,好比:计算≠计算机

[Java 8] 函数式编程简介

思维方式的转变 以从一个城市集合中寻找是否存在Chicago为例: 习惯的方式 boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); 以上代码就是绝大多数开发人员在面对这个问题时的第一反应.它通过命令式风格(Impera

Python函数式编程简介

函数 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计的基本单元. 函数式编程(Functional Programming) 函数式编程是一种抽象程度很高的编程范式.纯粹的函数式编程语言编写的函数没有变量的,因此任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用的.而非纯粹的函数(允许使用变量的),由于函数内部的变量状态不确定,

响应式编程 函数式编程 简介

响应式编程: 关键字:RxJava,观察者模式,EventBus,广播 理念:一切为事件 发出A事件通知B执行,而不是B一直等待(阻塞),提高效率. 函数式编程: 关键字:lambda,lisp,大数据,AI 理念: 1.一切为函数 2.每个函数是可靠的,没有副作用的:不能依赖外部环境/上下文 3.每一个可靠小函数组合成大函数,再组成一个牛逼函数 优点: 1.适合并行计算,每个小函数分发给不同CPU.非常适合大数据,AI这类大运算. 缺点: 1.难懂,反人类. 2.耗内存 虽然函数式编程最近又火

javaScript函数式编程

第1章 JavaScript函数式编程简介 11.1 JavaScript案例 11.2 开始函数式编程 41.2.1 为什么函数式编程很重要 41.2.2 以函数为抽象单元 71.2.3 封装和隐藏 91.2.4 以函数为行为单位 101.2.5 数据抽象 141.2.6 函数式JavaScript初试 171.2.7 加速 191.3 Underscore示例 221.4 总结 23第2章 一等函数与Applicative编程 242.1 函数是一等公民 242.2 Applicative编

JavaScript函数式编程(1):基本思想

1 函数式编程简介 函数式编程是和传统命令式编程区分的一种编程思想,"在函数式编程语言中,函数是第一类的对象,也就是说,函数 不依赖于任何其他的对象而可以独立存在,而在面向对象的语言中,函数 ( 方法 ) 是依附于对象的,属于对象的一部分.这一点决定了函数在函数式语言中的一些特别的性质,比如作为传出 / 传入参数,作为一个普通的变量等.[1]" 函数式编程思想的源头可以追溯到 20 世纪 30 年代,数学家阿隆左 . 丘奇在进行一项关于问题的可计算性的研究,也就是后来的 lambda

函数式编程介绍

本文将描述函数式编程的基本理论,从而让你理解什么是函数式编程,同时也会展示"函数式编程"和"面向对象"这两种不同风格的编程范式之间的区别.本文是函数式编程系列的入门篇,后续文章陆续会发出. 什么是函数 函数式编程背后的理论依据是数学,数学函数背后有一系列有意思的特点,而函数式编程语言则试图模拟这些特性. 让我们先来看一个简单的数学函数: y = x + 1 这个数学函数的意图是显而易见的,给定一个变量x然后返回x + 1, 上面的数学函数用C#来表示: public

函数式编程入门教程(转)

初涉函数式编程语言python,转载了一篇文章,先简单来了解一下函数式编程的概念. 文章原址:http://www.ruanyifeng.com/blog/2017/02/fp-tutorial.html 函数式编程入门教程 作者: 阮一峰 日期: 2017年2月22日 你可能听说过函数式编程(Functional programming),甚至已经使用了一段时间. 但是,你能说清楚,它到底是什么吗? 网上搜索一下,你会轻松找到好多答案. 与面向对象编程(Object-oriented prog