目录

  • 什么是函数式编程
    • 1. 函数式编程是一种开发范式
    • 2. 函数式编程起源于数学
  • 函数式编程的基本理论
    • 1. 函数是一等公民
    • 2. 使用到的函数必须是纯函数
    • 3. 追求纯函数的理由
  • 函数式编程的两个核心函数
    • 1. curry函数-用于将指定函数柯里化
    • 2. compose函数-组合函数
  • 使用函数式编程做个小栗子
    • 1. 面向过程编程示例
    • 2. 函数式编程示例
    • 3. 总结
  • 不可或缺的函子(functor)
    • 1. 啥是函子
    • 2. Maybe函子-判空
    • 3. Either函子

导读

  1. 最近杠上的是函数式编程,简称FP。最开始对它的接触大概是一年前左右,那是公司大牛做的一次技术分享。听的过程中是这样的感觉: 我X,还有这种操作,听完以后:我是谁,我在哪,我在干什么,最后仅仅模糊的记得一个概念:函数柯里化。由此我的脑子里稀里糊涂的多了一个概念:函数柯里化就是函数式编程。
  2. 随着这次对它系统的学习,才发现之前对FP的理解实在是太狭隘了,太狭隘了,太狭隘了…。下面就对自己的学习成果做下记录,后续也会随着自己的理解加深不断的纠错与补充。

阅读目标

  1. 理解函数式编程的概念
  2. 了解函数式编程使用的一些基本概念
  3. 认识并使用几种函子

什么是函数式编程

1. 函数式编程是一种开发范式

  • 常见开发范式有两种:面向过程和面向对象,而函数式编程就是将要接触的第三种开发范式
  • 在学习函数式编程时,要和面向过程对比着学,找到类似于刚从面向过程开发转到面向对象开发的那种感觉
  • 函数式编程和面向对象编程在某些地方有些类似,在学习的过程中脑袋里经常会有这样的疑问:这跟面向对象差不多嘛。此时你要做的就是忽略这种想法,这就是我提到第二点的原因。

2. 函数式编程起源于数学

  • 准确的说起源于数学中的一个分支:范畴学。我将它简单的归纳为:研究两个集合之间的关系。它的运算方法就是函数式编程,而这个方法正好可以用来学代码。
  • 函数式编程的起源决定了它的一个基调:使用到的函数必须是纯函数。因为数学本身就是一门不允许模棱两可的学问。
  • 如果对范畴学有兴趣,可以多多研究,可以加深对函数式编程的理解,反之,就多注意一下函数式编程的特点,同样也可以无压力的使用它,虽然我还没有做到。

函数式编程的基本理论

1. 函数是一等公民

我对他的理解有两部分:

  1. 每个函数要满足单一责任最小意外等原则
  2. 函数可以跟其他类型的数据一样,当做参数传递,赋值给变量,存放到数组中…

2. 使用到的函数必须是纯函数

  • 纯函数的定义:相同的输入,永远会得到相同的输出,而且没有可观察的副作用
  • 副作用的定义:在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。它可能包含但不限于如下操作:更改文件系统-往数据库插入记录-发送一个http请求-可变数据-打印log-获取用户输入-DOM查询-访问系统状态

3. 追求纯函数的理由

  1. 可缓存-移植性强-可测试性-引用透明
  2. 原来想对每个理由展开说明,后来感觉这些基本都见名知意,而且这也不是特别需要注意的内容,就不想占用过多篇幅,有问题留言即可

函数式编程的两个核心函数

如果要使用函数式编程我们需要借助一些第三方库,比如lodash(普通版本和FP版本)ramda,后续所有的示例都是基于FB lodash

1. curry函数-用于将指定函数柯里化

  • 函数柯里化:可以跟普通函数一样直接调用,也可只传递给函数传递一部分参数,让它返回一个函数去处理剩下的参数

    // 正常函数
    let add = function(x, y) {return x+y;
    }
    let result = add(1+1); // 2// 柯里化后的函数
    let curryAdd = _.curry(add);let addOne = curryAdd(1); // Function
    let addTen = curryAdd(10); // Functionlet result1 = addOne(1) // 2
    let result2 = addTen(1) // 11let result3 = curryAdd(1, 20) // 21
    

    至于原理,就是_.curry函数将我们add函数转化为如下类型的函数:

    // 通过闭包的方式保留参数x
    var add = function(x) {return function(y) {return x + y;};
    };
    
  • FP lodash除了提供_.curry函数帮助将自定义的函数转化成柯里化函数,FB lodash还将一些普通lodash版本中的函数封装成柯里化供我们使用,比如_.add/_.head/_.first等等,下面我们查看几个列子,更多查看FB lodash

      // 使用FB lodash中_.add直接实现addOne和addTenconst addOne = _.add(1);  // Functionconst addTen = _.add(10); // Functionconst result1 = addOne(1); // 2const result2 = addTen(10); // 20// 使用FB lodash中_.head获取数组的第一个元素const result3 = _.head([2,34,8); // 2const result4 = _.head('abcd); // a// _.heade的实现原理const getElementByIndex = function(index, arr) { // head原方法return arr[index];}const curryHead = _.curry(getElementByIndex);const getFirtElement = curryHead(0);getFirtElement([3,4,8]); // 3
    
  • 在柯里化的函数中,参数顺序也是有讲究的,仔细观察上面的这些柯里化后的函数,会发现我们都将要操作的数据放到了最后一个参数里,现在只需注意这点就行,后续就会慢慢明白为什么这样做了

2. compose函数-组合函数

  • 概念:将传入的函数组合起来,返回一个从右到左执行的管道函数

    let toUpperCase = function(x) { return x.toUpperCase(); };
    let exclaim = function(x) { return x + '!'; };
    let sayHi = function(x) { return 'Hi,' + x ; };
    let shout = _.compose(sayHi, toUpperCase, exclaim);shout('We are handsome'); // Hi,WE ARE HANDSOME!
    

    简易源码如下:

    // f和g都是函数,x是组合后形成函数的需传参数
    var compose = function(f,g) {return function(x) {// compose函数执行顺序都是从右到左return f(g(x));};
    };
    
  • compose函数其实就是帮助我们创建了一个从左到右的数据流,再加上每一步都是纯函数,大大增强了代码的可读性
  • 所有的compose都遵循一个规律:数学中的结合律。它可以让我们的组合更加灵活,而且肯定不会影响结果,举个栗子:
    let toUpperCase = function(x) { return x.toUpperCase(); };
    let exclaim = function(x) { return x + '!'; };
    let sayHi = function(x) { return 'Hi,' + x ; };// toUpperCase/exclaim/sayHi 三个函数只要保证顺序不变,随意我们组合,比如
    _.compose(sayHi, toUpperCase, exclaim)
    ==> _.compose(_.compose(sayHi, toUpperCase), exclaim)
    ==> _.compose(sayHi, _.compose(toUpperCase, exclaim));
    

使用函数式编程做个小栗子

需求描述:请求接口,将接口中的图片都渲染到页面中

1. 面向过程编程示例

function getDataAppendBody(word) {$.getJSON('https://api.flickr.com/services/feeds/photos_public.gne?tags=' + word + '&format=json&jsoncallback=?', (data) => {console.log(data);$('body').html(data.items.map((item) => {return $('<img />', { src: item.media.m });}));});
}
getDataAppendBody('dogs');

2. 函数式编程示例

/********************* 准备工作 ***************************/
// 强调: getJSON和setHtml都是柯里化函数
const Impure = {getJSON: _.curry(function(callback, url) {$.getJSON(url, callback);}),setHtml: _.curry(function(sel, html) {$(sel).html(html);})
};const img = function (url) {return $('<img />', { src: url });
};const trace = _.curry(function(tag, x) {console.log(tag, x);return x;
});const url = function (word) {return 'https://api.flickr.com/services/feeds/photos_public.gne?tags=' + word + '&format=json&jsoncallback=?';
};/********************* 开始操作 ***************************/
// _.map和_.prop 建议先
var images = _.compose(_.map(img),_.map(_.compose(_.prop('m'), _.prop('media'))), _.prop('items'));
var renderImages = _.compose(Impure.setHtml("body"), images);var app = _.compose(Impure.getJSON(renderImages), url);app("cats");

3. 总结

  • 函数式编程开发前,会将所有的步骤都处理成纯函数,而且这些纯函数的移植性都特别强,其他业务也都可以用。麻烦一次,永久方便,更爽的是可以拿这些纯函数,用_.compose随意组合,裂变出更多功能的函数,这点是面向对象做不到的
  • 但从代码量上看,其实面向过程完爆函数式编程,但从长远考虑,函数式编程留下了更强的扩展性,可读性也更加强(这一点等写习惯了才会慢慢了解)。
  • 在上面的栗子中有两个问题急需我们解决:如何判空如何捕捉异步的error,这就涉及到我们之后要接触的两个函子MaybeEither

不可或缺的函子(functor)

1. 啥是函子

functor 是实现了 map 函数并遵守一些特定规则的容器类型。 如下是一个最为基础的函子

const Container = function(val) {this.__value = val;
}
Container.of = function(x) {return new Container(x);
}
Container.prototype.map = function(f) {return Container.of(f(this.__value))
}Container.of(2).map(function(two){ return two + 2 })
Container.of("bombs").map(concat(' away')).map(_.prop('length'))

上面提到的规则,总结如下几个:

  • 只有一个属性,并且该属性可以是任意类型
  • of函数可有可无,它仅仅是用来避免在创建容器时避免忘记写new
  • map函数要返回一个新的函子对象,这样我们就可以连续map了
  • 函子的原型方法可以有扩展

2. Maybe函子-判空

首先看个栗子,针对如下对象取到name的值

const serverResponce = {company: { department: { name: 'xxxxx' } },
}

我们使用如下两种方式来取值,

 // 正常方式const name = serverResponce.company.department.name;// Maybe函子取值
const name = Maybe.of(serverResponce).map(_.prop('company')).map(_.prop('department')).map(_.prop('name'));

分析一波:

  1. 第一种方式,如果company和department某一项为null或undefine,程序将直接报错。当然可以在每层取值都进行判断,避免程序中断,但成大过大
  2. 第二种方式,如果出现中间某项为空,最终会返回一个Maybe.of(null),是否异常,只需判断name是否为Maybe.of(null)即可。

接下来研究一下Maybe函子的实现

const Maybe = function(val) {this.__value = val;
}Maybe.of = function(x) {return new Maybe(x);
}Maybe.prototype.isNothing = function() {return (this.__value === null || this.__value === undefine);
}// 还没有搞清楚怎么数组结构
Maybe.prototype.map = function(f) {return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value))
}

跟Container主要区别,就是多了一个函数isNothing,接着在每次调用Map的时候优先执行一下this.isNothing(),如果为空就直接返回一个Maybe.of(null)

3. Either函子

除了Either函子,还有IO/Task/Monad…, 后续可能会在单独开一篇博客详细介绍函子。

系统研究一下函数式编程相关推荐

  1. 2021年大数据常用语言Scala(二十一):函数式编程 遍历 foreach

    目录 遍历  foreach 使用类型推断简化函数定义 使用下划线来简化函数定义 遍历  foreach 之前,学习过了使用for表达式来遍历集合.我们接下来将学习scala的函数式编程,使用fore ...

  2. 2021年大数据常用语言Scala(二十):函数式编程 介绍

    目录 函数式编程 介绍 函数式编程的意义在哪? 函数式编程 介绍 我们将来使用Spark/Flink的大量业务代码都会使用到函数式编程.下面的这些操作是学习的重点. 现在我们将会逐渐接触函数式编程的方 ...

  3. Scala函数式编程(三) scala集合和函数

    前情提要: scala函数式编程(二) scala基础语法介绍 scala函数式编程(二) scala基础语法介绍 前面已经稍微介绍了scala的常用语法以及面向对象的一些简要知识,这次是补充上一章的 ...

  4. scala函数式编程(二) scala基础语法介绍

    上次我们介绍了函数式编程的好处,并使用scala写了一个小小的例子帮助大家理解,从这里开始我将真正开始介绍scala编程的一些内容. 这里会先重点介绍scala的一些语法.当然,这里是假设你有一些ja ...

  5. [2017.02.23] Java8 函数式编程

    以前学过Haskell,前几天又复习了其中的部分内容. 函数式编程与命令式编程有着不一样的地方,函数式编程中函数是第一等公民,通过使用少量的几个数据结构如list.map.set,以及在这些数据结构上 ...

  6. 惰性求值 php,详细介绍C#函数式编程的示例代码

    public double MemoryUtilization() { //计算目前内存使用率 var pcInfo = new ComputerInfo(); var usedMem = pcInf ...

  7. java 函数式编程_函数式编程杂谈

    比起命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断演进,逐层推导出复杂的运算.本文通过函数式编程的一些趣味用法来阐述学习函数式编程的奇妙之处. 一 ...

  8. 简明python教程 --C++程序员的视角(九):函数式编程、特殊类方法、测试及其他...

    函数式编程 Lambda exec,eval和assert语句,repr函数 lambda语句 用来创建简短的单行匿名函数 print_assign = lambda name, value: nam ...

  9. spark编程基础--2.4函数式编程基础

    foreach遍历操作 映射操作map,flatmap 过滤操作filter 规约操作 reduce,fold方法 拆分操作partition,groupedBy,grouped,sliding Sc ...

最新文章

  1. Web技术栈中不可或缺的Linux技术
  2. android 圆滑曲线,如何使用贝塞尔曲线在一组点上绘制平滑线?
  3. 第一次写blog,哈哈
  4. openstack运维实战系列(十七)之glance与ceph结合
  5. 安装Pytorch如何选择CUDA的版本
  6. [转]程序员应打破牢笼,展望更高层次的世界
  7. Android 系统自带的图标
  8. 最全的TCP面试知识点
  9. [Codeforces Round #152 (Div. 2)]A. Cupboards
  10. pure-ftpd 配置
  11. 【100个 Unity小知识点】☀️ | Unity中显示运行时游戏帧率的方法
  12. html不能默认IE浏览器,编辑告诉您win7如何设置ie为默认浏览器的完全解决办法
  13. 非靶向代谢组学数据分析方法总结
  14. clion-debug调试步骤
  15. 运动蓝牙耳机什么牌子好,骑行运动耳机
  16. mac 阿里云ecs配置php,在Mac OS下配置PHP开发环境
  17. 华为正式发布鸿蒙 2.0,更新人数太多挤爆服务器,P50 也官宣了!
  18. springbootsecurity实现权限管理详细步骤
  19. drawio界面自定义配置
  20. 用Python可以解决的数学问题,探究代数、统计、几何、概率等

热门文章

  1. 核密度聚类(二)核密度估计、自适应核密度的数学原理
  2. 在Mac上查看文件在终端中的绝对完整路径
  3. ZOJ 3466 The Hive II 插头DP
  4. anchorwave进行复杂基因组比对(1)
  5. 2022-1-21 Leetcode 599. 两个列表的最小索引总和
  6. 有人做脑电的情绪分类吗
  7. 静电放电(ESD)最常用的三种模型及其防护设计
  8. php实现删除数据库中内容,php – 从数据库中删除内容,安全预防措施
  9. cannot import name ‘load_offloaded_weights‘ from ‘accelerate.utils‘(安装transformers)
  10. img加载本地图片_图片加载技术-懒加载和预加载