2023年再看函数式编程

2017年我写过一篇文档关于函数式编程,那是主要用的还是OC 语言。6年过去了再看函数式编程感觉当时还是青涩。 《iOS 面向函数编程的理解》

最初接触函数式编程还是Rx 系列响应式的概念带来的,这么多年用过Rxswift,Rxjs ,一直理解不够深刻。

React 带来的hooks, 官方概念是利用函数式编程方式,更好的组合,开发和测试。但是还是觉得不够深刻,又看了些资料,梳理下自己的理解,重点关注react 中的提现。

概念

函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。

除了函数式编程方式,还有:

  1. 面向对象编程
  2. 面向过程编程
  3. 命令式编程

纯函数

纯函数是指在函数的执行过程中,不会对程序的状态进行任何改变,也不会对外部环境产生任何副作用,即只依赖于其输入参数,而不依赖于任何外部变量或状态的函数。

纯函数的特征

1、相同的输入总是产生相同的输出,即函数的输出只由输入决定,不受外部状态或副作用的影响。

2、函数对外部状态没有依赖,也不会改变外部状态,即不会对程序的其他部分产生任何副作用。

3、函数不会修改传入的参数,而是返回一个新的值,保持输入参数的不可变性。

4、函数的执行过程对于调用者来说是透明的,即调用者不需要了解函数的内部实现细节,只需要关注输入和输出。

举例

纯函数:

js 复制代码
function sum(a,b) {
  return a + b;
}

非纯函数(引入外部变量):

js 复制代码
var c = 0
function sum(a,b) {
  return a + b + c;
}

非纯函数(副作用):

js 复制代码
function sum(a,b) {
  console.log(a);
  return a + b ;
}

纯函数的优势

  • 可靠,输出是由输入决定
  • 可测试性强
  • 可缓存
  • 没有副作用,利于代码维护重构以及并行处理

JS 中函数编程思想应用

函数编程思想,函数是一等公民,输入沿着函数管道组合产生想要的结果。

下面这个例子利用map、filter、reduce 等函数对入参一个对象数组进行加工,这是一个简单的函数式编程的思想应用,array 每次经过纯函数的加工,返回结果作为输出再次加工。

js 复制代码
let arr  = [{key: 'a', value: 1},{key: 'b', value: 2},,{key: 'c', value: 3}]
arr.map((item)=>item.value).filter((item)=>item !==2).reduce((t,i)=>t+i,0)

一个输入到达终点的路径很多,路径都是一个个函数。所以函数之间的调用关系也是非常重要的优化手段。

函数柯里化

定义:把接收多个参数的函数,转成接收单一参数的函数,并且返回余下参数的函数的过程就叫做函数柯里化

举例

js 复制代码
// 正常函数
function add(a, b){
   return a + b
}
 
add(1, 2)
 
// 转成柯里化函数
function add(a){
    return function(b){
        return a + b
    }
}
 
add(1)(2)

为什么要柯里化

存在即合理,柯里化的使用场景是哪些呢?

先简单的变化一下上面的例子:

js 复制代码
// 正常函数
function add(a, b){
   return a + b
}
 
add(1, 2)
 
// 转成柯里化函数
function add(a){
    return function(b){
        return a + b
    }
}
 
let f1 = add(1)
let add2 = f1(2)
let add3 = f1(3)

这样大概可以看出来柯里化的意义,但是场景不是很合理。下面是一个正则的例子:

js 复制代码
// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}

// 即使是相同的正则表达式,也需要重新传递一次
console.log(check(/\d+/g, 'test1')); // true
console.log(check(/\d+/g, 'testtest')); // false
console.log(check(/[a-z]+/g, 'test')); // true

// Currying后
function curryingCheck(reg) {
    return function (txt) {
        return reg.test(txt)
    }
}

// 正则表达式通过闭包保存了起来
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

console.log(hasNumber('test1')); // true
console.log(hasNumber('testtest'));  // false
console.log(hasLetter('21212')); // false

上面的示例是一个正则的校验,正常来说直接调用 check 函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数 reg 进行复用,这样别的地方就能够直接调用 hasNumber、hasLetter 等函数,让参数能够复用,调用起来也更方便。

柯里化封装

js 复制代码
// 支持多参数传递
function currying(fn, ...args) {
 
    var self = this
    var len = fn.length;
    var args = args || [];
 
    return function() {
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);
 
        // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
        if (_args.length < len) {
            return currying.call(self, fn, _args);
        }
 
        // 参数收集完毕,则执行fn
        return fn.apply(this, _args);
    }
}

compose 函数

compose 函数可以接收多个独立的函数作为参数,然后将这些函数进行组合串联,最终返回一个"组合函数"。

compose 函数的实现:

js 复制代码
function compose(...funcs) {
  if (funcs.length === 0) {
      return arg => arg
  }
  if (funcs.length === 1) {
      return funcs[0]
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

举例说明:

js 复制代码
const add = (a) => a + 1
const mul = (a) => a * 10
const f = compose(add, mul)
console.log(f(1))

结果: 11

这个无需多解释,就是把函数由右向左执行,

pipe 函数

js 复制代码
function compose(...funcs) {
  if (funcs.length === 0) {
      return arg => arg
  }
  if (funcs.length === 1) {
      return funcs[0]
  }
  return funcs.reduceRight((a, b) => (...args) => a(b(...args)))
}

举例说明:

js 复制代码
const add = (a) => a + 1
const mul = (a) => a * 10
const f = compose(add, mul)
console.log(f(1))

结果: 20

我理解,柯里化和组合、pipe 应该会结合理解使用。

相关推荐
Oberon13 天前
从零开始的函数式编程(2) —— Church Boolean 编码
数学·函数式编程·λ演算
桦说编程22 天前
CompletableFuture 超时功能有大坑!使用不当直接生产事故!
java·性能优化·函数式编程·并发编程
桦说编程25 天前
如何安全发布 CompletableFuture ?Java9新增方法分析
java·性能优化·函数式编程·并发编程
桦说编程1 个月前
【异步编程实战】如何实现超时功能(以CompletableFuture为例)
java·性能优化·函数式编程·并发编程
氦客1 个月前
Android Compose 显示底部对话框 (ModalBottomSheet),实现类似BottomSheetDialog的效果
android·dialog·ui·compose·modal·bottomsheet·底部对话框
鱼樱前端1 个月前
Vue3之ref 实现源码深度解读
vue.js·前端框架·函数式编程
RJiazhen2 个月前
前端项目中的函数式编程初步实践
前端·函数式编程
小林爱2 个月前
【Compose multiplatform教程06】用IDEA编译Compose Multiplatform常见问题
android·java·前端·kotlin·intellij-idea·compose·多平台
小林爱2 个月前
【Compose multiplatform教程12】【组件】Box组件
前端·kotlin·android studio·框架·compose·多平台
俺不理解2 个月前
Android Compose 悬浮窗
android·生命周期·compose·悬浮窗