函数式编程初探

1 文章目标

  • 为什么要学习函数式编程以及什么是函数式编程
  • 函数式编程的特性(纯函数、柯里化、函数组合等)
  • 函数式编程的应用场景
  • 函数式编程库Lodash
2 什么是函数式编程

阮一峰老师的函数式编程入门教程:http://www.ruanyifeng.com/blog/2017/02/fp-tutorial.html
Franklin Risby 教授的函数式编程指北:https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch1.html
关于什么是函数式编程,就不多说什么了,给两个大神的链接给各位朋友瞅瞅。以下记录以下函数式编程中重要的知识点

3 闭包

函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。(MDN对于闭包的定义https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures)

通过一个只执行一次的函数的例子,了解一下闭包的使用方式

function once(){
    let done = false;
    return function(){
        if(!done){
            console.log(done);
            done = true;
        }
    }
}

let f = once();
f()
f()
...

上面这个函数,无论调用多少次,只有打印第一次。 f引用的是once内部的函数。在外面,我们通过调用f可以访问到once函数的作用域。

4 纯函数
  • 对于相同的输入,永远得到相同的输出。它不依赖于程序执行期间函数外部任何状态或数据的变化,只依赖于输入参数
  • 除了纯函数以外的任何变动,都不影响纯函数
  • 纯函数还使得维护和重构代码变得更加容易,你可以放心的修改某个纯函数,不必关心改动会影响其它地方
  • 由于对于相同的输入,永远得到相同的输出,所以纯函数可以缓存,之后调用传入相同参数是,不用执行,直接获取之前计算的值
    纯函数缓存例子
function memorize(fn){
    let caches = {} // 用于缓存之前的计算
    return function(){
        let arg_str = JSON.stringify(arguments)
        caches[arg_str] = caches[arg_str] || fn.apply(null,arguments);
        return caches[arg_str]
    }
}

function sum(a ,b){
    console.log(a,b); // 从这里可以看出执行了几次sum函数
    return a + b;
}

let sumM = memorize(sum)

console.log(sumM(1,2));
console.log(sumM(2,2));
console.log(sumM(1,2));
5 柯里化
  • 把一个多参数的函数,转化为单参数函数。
  • 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
  • 这是一种对函数参数的’缓存’
  • 让函数变得更灵活,让函数的粒度更小
  • 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能
    这里看到一位朋友写的关于柯里化的也不错:https://www.jianshu.com/p/2975c25e4d71
    下面是对于柯里化的使用例子
function curry(fn){
    return function curried(...args){
        // args还用来保存上一步的参数
        if(fn.length > args.length){
            return function(){
                return curried(...args.concat(Array.from(arguments)))
            }
        }
        return fn(...args)
    }
}

function add(a, b, c){
    return a + b + c
}

let cAdd = curry(add)

console.log(cAdd(1)(2)(3));
console.log(cAdd(1,2)(3));
console.log(cAdd(1,2,3));
console.log(cAdd(1)(2,3));
6 compose组合

WechatIMG81.png
如图,现在有这么一个操作,数据a经过f函数处理后在经过g函数处理后得到c;代码操作入下

function f(x){
    return x + 1
}
function g(x){
    return x * x
}
console.log(g(f(2)));

如果增加一些操作就会形如以下a(b(c(d(e())))); 为了处理这样的函数,就需要组合一下函数了,使我们最后能够使用f(x)就能得到结果

function compose(...args){
    return function(x){
         return args.reduce(function(total,fn){
            return fn(total)
        },x)
    }
}
let p = console(f,g)

简化compose

let compose = (...args) => x => args.reduce((total,fn) => fn(total), x);
7 函子

有些副作用是不可避免的,但是使用函子,可以将副作用控制在可控范围内。

7-1 什么是副作用

函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。副作用的函数不仅仅只是返回了一个值,而且还做了其他的事情。这里有一边关于副作用的文章:http://www.fly63.com/article/detial/1176

副作用如下
1、修改了一个变量
2、直接修改数据结构
3、设置一个对象的成员
4、抛出一个异常或以一个错误终止
5、打印到终端或读取用户输入
6、读取或写入一个文件
7、在屏幕上画图

7-2 什么是函子
  • 是一个特殊的容器,通过一个普通对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)
  • 使用函子可以实现链式编程
    这个例子只提到关于链式编程,8以下都是对于函子锁引出的问题进行解决(将副作用控制的可控范围内)
class Functor {
    // 为了使用这个函子的时候可以不在外部显示的使用new functor,添加一个静态的of方法
    static of(value){
        return new Functor(value)
    }
    constructor(value){
        this._value = value
    }
    map(fn){
        return Functor.of(fn(this._value))
    }
    getVal(){
        return this._value
    }
}

let p = Functor.of(2).map(x => x + 2).map(x => x * 2).getVal()
console.log(p);
7-3 总结
  • 函数式编程的运算不直接操作值,而是由函子完成
  • 函子就是一个实现了map契约的对象
  • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
  • 想要处理盒子中的值,我们需要给盒子的map方法穿第一个处理值的函数(纯函数),由这个函数来对值进行处理
  • 最终map方法返回一个包含新值的盒子(函子)
8 MayBe函子
  • 我们在编程过程中可能会遇到很多错误,需要对这些错误进行相应的处理
  • MayBe函子的作用就是可以对外部的空值进行处理(控制副作用在允许范围内)
// 由于传入为空,不能执行转为大写操作,报错
Functor.of(null).map(x => x.toUpperCase())
class MayBe extends Functor{
    static of(value){
        return new MayBe(value)
    }
    map(fn){
        return this._value ? Functor.of(fn(this._value)) : Functor.of(null)
    }
}

let p2 = MayBe.of(null).map(x => x.toUpperCase()).getVal()
console.log(p2);
9 Either

Either 并不仅仅只对合法性检查这种一般性的错误作用非凡,对一些更严重的、能够中断程序执行的错误比如文件丢失或者 socket 连接断开等,Either 同样效果显著。这里,我仅仅是把 Either 当作一个错误消息的容器介绍给你!

class Left{
   static of(value){
       return new Left(value)
   }
   constructor(value){
       this._value = value
   }
   map(fn){
       return this
   }
}
class Right{
   static of(value){
       return new Right(value)
   }
   constructor(value){
       this._value = value
   }
   map(fn){
       return Right.of(fn(this._value))
   }
}

function parseJSON(str){
   try{
       return Right.of(JSON.parse(str))
   }catch(err){
       return Left.of({message: err.message})
   }
}

let p = parseJSON('hello world')
console.log(p);
10 IO函子
  • IO函子中的_value是一个函数,这里是把函数最为值来处理
  • IO函子可以把不纯的动作存储到_value中,延迟执行这个不纯的操作(惰性执行),包装当前的操作为纯
  • 把不纯的操作交给调用者处理
const fp = require('lodash/fp')

class IO{
    static of(value){
        return new IO(function(){
            return value
        })
    }
    constructor(fn){
        this._value = fn
    }
    map(fn){
        return new IO(fp.flowRight(fn,this._value))
    }
}

let f = new IO(process).map(p => p.execPath)

11 Folktale
  • 异步任务的实现过于复杂,使用folktale中的task来演示
  • folktale是一个标准的函数式编程库
  • 和lodash、ramda、不同的是,他没有提供很多功能函数
  • 只是提供了一些函数式处理的操作,例如:compose、curry等,一些函子 Task、Either、MayBe 等
    使用task函子执行异步任务
// npm i folktale

// Task 处理异步任务
const fs = require('fs')
const {task} = require('folktale/concurrency/task')
const {split, find} = require('lodash/fp')

function readFile(filename){
    return task(resolver => {
        fs.readFile(filename,'utf-8',(err,data) => {
            if(err)resolver.reject(err)
            resolver.resolve(data)
        })
    })
}

readFile('package.json') // 返回task函子
.map(split('\n'))
.map(find(x => x.includes('version')))
.run()
.listen({
    onRejected: err => {
        console.log(err);
    },
    onResolved: value => {
        console.log(value);
    }
})
12 IO函子的问题

函子嵌套了

const fs = require('fs')
const fp = require('lodash/fp')

class IO{
    static of(value){
        return new IO(function(){
            return value
        })
    }
    constructor(fn){
        this._value = fn
    }
    map(fn){
        return new IO(fp.flowRight(fn,this._value))
    }
}

let readFile = function(filename){
    return new IO(function(){
        return fs.readFileSync(filename,'utf-8')
    })
}

let Print = function(x){
    return new IO(function(){
        console.log(x);
        return x
    })
}

let cat = fp.flowRight(Print, readFile)
let r = cat('package.json')
console.log(r._value()._value());
13 Monad函子

解决函子嵌套的问题

  • Monad 函子是可以变扁的 Pointed(有静态 of方法的) 函子, IO(IO(x))
  • 一个函子如果具有join和of两个方法并遵守一些定律就是一个Monad
const fs = require('fs')
const fp = require('lodash/fp')

class IO{
    static of(value){
        return new IO(function(){
            return value
        })
    }
    constructor(fn){
        this._value = fn
    }
    map(fn){
        return new IO(fp.flowRight(fn,this._value))
    }
    join(){
        return this._value()
    }
    flatMap(fn){
        let s = this.map(fn).join()
        console.log(1,s);
        return s
    }
}

let readFile = function(filename){
    return new IO(function(){
        let file = fs.readFileSync(filename,'utf-8')
        console.log(file);
        return file
    })
}

let Print = function(x){
    console.log("flatMap中join:执行读取,并且执行打印,结束后就是读取完数据,并且返回打印中那个函子")
    console.log(x);
    return new IO(function(){
        return x
    })
}

let cat = readFile('package.json')
.flatMap(Print)

console.log(cat);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JS 函数式编程指南 中文版 This is the Simplified Chinese translation of mostly-adequate-guide, thank Professor Franklin Risby for his great work! 关于本书 这本书的主题是函数范式(functional paradigm),我们将使用 JavaScript 这个世界上最流行的函数式编程语言来讲述这一主题。有人可能会觉得选择 JavaScript 并不明智,因为当前的主流观点认为它是一门命令式(imperative)的语言,并不适合用来讲函数式。但我认为,这是学习函数式编程的最好方式,因为: 你很有可能在日常工作中使用它 这让你有机会在实际的编程过程中学以致用,而不是在空闲时间用一门深奥的函数式编程语言做一些玩具性质的项目。 你不必从头学起就能开始编写程序 在纯函数式编程语言中,你必须使用 monad 才能打印变量或者读取 DOM 节点。JavaScript 则简单得多,可以作弊走捷径,因为毕竟我们的目的是学写纯函数式代码。JavaScript 也更容易入门,因为它是一门混合范式的语言,你随时可以在感觉吃力的时候回退到原有的编程习惯上去。 这门语言完全有能力书写高级的函数式代码 只需借助一到两个微型类库,JavaScript 就能模拟 Scala 或 Haskell 这类语言的全部特性。虽然面向对象编程(Object-oriented programing)主导着业界,但很明显这种范式在 JavaScript 里非常笨拙,用起来就像在高速公路上露营或者穿着橡胶套鞋跳踢踏舞一样。我们不得不到处使用 bind 以免 this 不知不觉地变了,语言里没有类可以用(目前还没有),我们还发明了各种变通方法来应对忘记调用 new 关键字后的怪异行为,私有成员只能通过闭包(closure)才能实现,等等。对大多数人来说,函数式编程看起来更加自然。 以上说明,强类型的函数式语言毫无疑问将会成为本书所示范式的最佳试验场。JavaScript 是我们学习这种范式的一种手段,将它应用于什么地方则完全取决于你自己。幸运的是,所有的接口都是数学的,因而也是普适的。最终你会发现你习惯了 swiftz、scalaz、haskell 和 purescript,以及其他各种数学偏向的语言。 ---------------------------------------------------- 本 PDF 基于开源文档,目录书签齐全。 版权归原作者,翻译版权归译者。 ----------------------------------------------------
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。在编写C程序时,需要注意变量的声明和定义、指针的使用、内存的分配与释放等问题。C语言中常用的数据结构包括: 1. 数组:一种存储同类型数据的结构,可以进行索引访问和修改。 2. 链表:一种存储不同类型数据的结构,每个节点包含数据和指向下一个节点的指针。 3. 栈:一种后进先出(LIFO)的数据结构,可以通过压入(push)和弹出(pop)操作进行数据的存储和取出。 4. 队列:一种先进先出(FIFO)的数据结构,可以通过入队(enqueue)和出队(dequeue)操作进行数据的存储和取出。 5. 树:一种存储具有父子关系的数据结构,可以通过中序遍历、前序遍历和后序遍历等方式进行数据的访问和修改。 6. 图:一种存储具有节点和边关系的数据结构,可以通过广度优先搜索、深度优先搜索等方式进行数据的访问和修改。 这些数据结构在C语言中都有相应的实现方式,可以应用于各种不同的场景。C语言中的各种数据结构都有其优缺点,下面列举一些常见的数据结构的优缺点: 数组: 优点:访问和修改元素的速度非常快,适用于需要频繁读取和修改数据的场合。 缺点:数组的长度是固定的,不适合存储大小不固定的动态数据,另外数组在内存中是连续分配的,当数组较大时可能会导致内存碎片化。 链表: 优点:可以方便地插入和删除元素,适用于需要频繁插入和删除数据的场合。 缺点:访问和修改元素的速度相对较慢,因为需要遍历链表找到指定的节点。 栈: 优点:后进先出(LIFO)的特性使得栈在处理递归和括号匹配等问题时非常方便。 缺点:栈的空间有限,当数据量较大时可能会导致栈溢出。 队列: 优点:先进先出(FIFO)的特性使得
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值