su37josephxia / frontend-interview

前端面试知识点
MIT License
159 stars 45 forks source link

解释一下科里化和组合 #137

Open su37josephxia opened 2 years ago

shangjunhao commented 2 years ago

理解

柯里化, 又叫 部分求值, 目的是使函数理解并处理部分应用, 核心的思想是 把接收多个参数的函数转化为接收一个(部分)参数并返回调用下个参数的函数.

所谓的柯里化, 简单来说就是把原本接收多个参数的函数修改为接收一个参数的多层函数, 例

let res = fun(a, b, c, d)

let res = fun(a)(b)(c)(d)

作用

1、参数的复用 2、提前返回部分结果 3、延迟后续的运算过程

实现

// 简单版
let curry = function(fn) {
    const that = this
    let args = Array.prototype.slice.call(arguments, 1)
    return function() {
        const innerArgs = Array.prototype.slice.call(arguments)
        args = args.concat(...innerArgs)
        if (args.length < fn.length) {
            return curry.call(that, fn, ...args)
        }
        return fn.apply(this, args)
    }
}

反柯里化

RJM1996 commented 2 years ago

柯里化与组合(curry&compose)

柯里化

将一个多参数的函数, 转换为一个单参数的函数, 并且新的函数可以继续接收剩余的参数

例如:

function add(a, b, c) {
  return a + b + c
}
// 通过柯里化函数 curry 进行转换
const _add = curry(add)
// 结果和使用 add(1, 2, 3) 一样
_add(1)(2)(3)

严格来说柯里化后的函数只能接收一个参数, 但是为了实际使用更加方便, 也可以改成能够接收多个参数

// 结果都是一样的
add(1, 2, 3, 4, 5)
add(1)(2)(3, 4, 5)
add(1, 2)(3, 4)(5)
add(1)(2)(3)(4)(5)

编写柯里化函数

/**
 * 将函数柯里化
 * @param fn    待柯里化的原函数
 * @param len   所需的参数个数,默认为原函数的形参个数
 */
module.exports = function curry(fn, len = fn.length) {
  return _curry.call(this, fn, len)
}

/**
 * 中转函数
 * @param fn    待柯里化的原函数
 * @param len   所需的参数个数
 * @param args  已接收的参数列表
 */
function _curry(fn, len, ...args) {
  return function (...params) {
    let _args = [...args, ...params]
    // 当函数参数个数等于原函数需要的参数个数时, 就开始执行
    if (_args.length >= len) {
      return fn.apply(this, _args)
    } else {
      // 否则就继续柯里化
      return _curry.call(this, fn, len, ..._args)
    }
  }
}

柯里化的优点:

组合

函数组合就是将一个函数的输出作为另一个函数的输入, 经过若干次组合, 最后获得最终的结果

例如有一个需求: 将一个字符串转换为大写, 再反转 我们一般会写一个函数, 先将字符串转换为大写, 再将这个结果进行反转

function test() {
  function fn(str) {
    const a1 = str.toUpperCase()
    const a2 = a1.split('').reverse().join('')
    return a2
  }
  console.log(fn('hello'))
}

如果使用函数组合, 可以这样实现 ps: 通常我们使用 compose 函数来对函数进行组合

function test2() {
  const strToUpperCase = (str) => str.toUpperCase()
  const strReverse = (str) => str.split('').reverse().join('')
  function compose(f, g) {
    return function (str) {
      return g(f(str))
    }
  }
  const strToUpperAndReverse = compose(strToUpperCase, strReverse)
  console.log(strToUpperAndReverse('hello'))
}
test2()

看起来变得更麻烦了, 但是如果我们的需求变了, 需要再将字符串转为数组, 那么第一种方式我们就需要修改原来封装的函数, 但是使用函数组合, 我们只需要再写一个将字符串转为数组的函数就好了

const strToArray = (str) => str.split('') // 新增一个工具函数

const strToUpperAndReverse = compose(
  strToUpperCase,
  strReverse,
  strToArray // 进行组合
)

所以函数组合就是将函数功能单一化, 然后再将这些单一功能的函数进行组合, 实现更复杂的逻辑, 就像搭积木一样, 方便扩展 我们上面实现的 compose 函数只能处理有限个参数, 通常 compose 函数可以接受任意个函数进行组合, 我们可以使用 reduce 实现

function compose(...fns) {
  return function (x) {
    return fns.reduce((arg, fn) => fn(arg), x)
  }
}
crazyyoung1020 commented 2 years ago
  1. 柯里化函数是将多个参数的函数,转化成分布计算,一次传一个或多个参数,直到所有参数全部传入了才会返回结果。
    
    // 如有柯里化函数currey
    有待处理函数fn = (x,y,x)=>{}

那么fn经过柯里化之后可以得到fn_curried = currey(fn)

fn_curried的使用如下 fn_curried(x)(y)(z)


2. 组合函数即将多个函数组合起来执行,使用场景为,需要连续调用多个函数,前一个函数的执行结果为后一个函数的参数,那么我们可以将这些函数组合起来,得到一个新的函数,然后传入参数执行即可

```js
如有组合函数compose
有函数foo,baz,bar
我们有个需求需要先调用foo,再调用baz,再调用bar,然后得到最终结果
我们可以使用组合函数得到新的函数 com = compose(bar, baz, foo)
然后执行com(x)可以得到结果

柯里化都是利用了闭包的原理,将参数保存在闭包内,可以做让函数分布执行或者延迟执行

7TingYu commented 2 years ago

函数颗粒化(curry)

函数颗粒化,就是将一个多参数的函数转化为新的函数,只接受部分的参数,而且新的函数可以继续接收剩余的参数

柯里化实际是把简单的函数执行流程复杂化,但是复杂化的同时,我们在使用函数时拥有了更加多的自由度。

函数组合(composition)

函数组合就是一种将已被分解的简单任务组合成复杂任务的过程。

yanzefeng commented 2 years ago

柯里化 柯里化(currying)指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只接受一个参数(unary)。 函数组合 函数组合将多个函数合并成一个函数,使用场景为,需要连续调用多个函数,前一个函数的执行结果为后一个函数的参数,那么我们可以将这些函数组合起来,得到一个新的函数,然后传入参数执行即可

zcma11 commented 2 years ago

柯里化是通过闭包保存传入的参数,直到参数凑齐才执行函数否则一直返回一个函数接受参数,实现了参数复用,惰性计算。

function curry(fn) {
  let _args = []
  const length = fn.length
  const c = (...args) => {
    _args = [..._args, ...args]
    if (_args.length === length) {
      return fn.apply(null, _args)
    } else {
      return c
    }
  }

  return c
}

compose 是将嵌套的函数调用改成了扁平的方式,将上一个的函数的返回值作为下一个函数的参数。

function compose(...funs) {
  return (...args) => {
    let i = funs.length - 1
    let res = funs[i](...args)
    while (i--) {
      res = args = funs[i](res)
    }

    return res
  }
}

// 或者使用 reduce 也可以
function ccomposs(...funs) {
  return (args) => funs.reduceRight((a, b) => b(a), args)
}
jiafei-cat commented 2 years ago

科里化可以通过分步传参实现偏函数,在函数式编程里面的意义是,它可以将一个函数转为接受一个参数返回一个值的固定形式,这样对于函数组合非常有用,而且科里化后我们只会关注函数本身作用而不被参数影响。而compose组合的意义就是组合函数,作用是将多个函数串联执行,就像流水线一样处理商品一样,商品在这个车间处理完成,又去下一个车间处理

rachern commented 2 years ago

柯里化

柯里化是将一个多参数的函数转化成分步传参的函数形式,每次只传一个或者多个参数

组合函数

组合函数能够将多个函数组合起来调用,类似流式,一个函数的输出结果是下一个函数的输入

rachern commented 2 years ago

柯里化

柯里化是将一个多参数的函数转化成分步传参的函数形式,每次只传一个或者多个参数

组合函数

组合函数能够将多个函数组合起来调用,类似流式,一个函数的输出结果是下一个函数的输入

ruixue0702 commented 2 years ago

解释一下科里化和组合

1.柯里化函数:一种由需要多个参数的函数转化为一次只接受一个参数的函数,并且通过使用第一个参数并返回一系列函数直到所有的参数都已被收集 const add = a => b => a + b;

  1. 组合 :在函数中创建了一个管道,把一个函数的输出与下一个函数的输入连接起来 const compose = (f, g) => f(g(x))

  2. 柯里化函数对于函数组合非常有用,因为由于函数组合的需要,你可以把 n 元函数轻松地转换成一元函数形式:管道内的函数必须是单一参数

lkzwc commented 2 years ago

函数的柯粒化

柯粒化是指将多个参数的函数转化为一个参数的函数,并返回该函数的调用

函数组合

是指将函数的返回结果作为另外一个函数的的输入

原理实现

str ="hello"

function a(params) {
    return params.toUpperCase()
}
function b(params) {
    return params.split('').reverse().join('')
}

const com=str=>(a,b)=>{
    return a(b(str))
}

console.log("////",com(str)(a,b))

function com1(str,a,b){
    return a(b(str))
}

const com2=str=>a=>b=>{
    return a(b(str))
}
console.log("222",com2(str)(a)(b))