博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
函数式点滴--partial&curry
阅读量:7099 次
发布时间:2019-06-28

本文共 5040 字,大约阅读时间需要 16 分钟。

介绍

函数式有不少处理输入参数的工具方法

identity

function identity(v) {  return v}复制代码

unary

function unary(fn) {  return function onlyOneArg(arg) {    return fn(arg)  }}复制代码

spreadArgs

function spreadArgs(fn) {  return function spreadFn(argsArr){    return fn(...argsArr)  }}复制代码

gatherArgs

function gatherArgs(fn) {  return function gatheredFn(...argsArr) {    return fn(argsArr)  }}复制代码

reverseArgs

function reverseArgs(fn) {  return function argsReversed(...args) {    return fn(...args.reverse())  }}复制代码

...

...

一切都很简单,确实,有的函数(如identity)简单到你可能都不知道它能用来做什么

函数式里的函数就像积木一样,每块积木看上去都是这么简单
至于怎么"堆"积木,可以用compose来组合他们,以后再关注这些方法的具体使用

这次主要是来学习一下,partial和curry这两个对输入参数处理的方法,其实它们是js本身就有的功能,只不过函数式更多使用它们作为工具。

partial

举个例子, 你有一个ajax函数

function ajax(url, data, callback) {  // ...  }复制代码

你事先知道url, 但是data, callback可能要等一会(比如等用户输入完表单)才知道

(当然你可以等输入参数都ok,再调用)
这里可以创建一个新函数,内部调用ajax,并传入url, 等待data, callback参数

function getPerson(data,cb) {  ajax( "http://some.api/person", data, cb );}复制代码
function getOrder(data,cb) {  ajax( "http://some.api/order", data, cb );}复制代码

很快,手动操作,就会变得很无趣,特别是如果已知参数变化,比如我们不仅事先知道url还知道data

function getCurrentUser(cb) {  getPerson( { user: CURRENT_USER_ID }, cb );}复制代码

这时候我们需要寻找一个较为通用的工具方法 仔细观察发现,我们将部分实参预先应用到形参,而将剩余的实参推迟应用

partial(部分应用或偏函数应用) -- 其可以减少函数的输入参数的个数

// sq:function partial(fn, ...presetArgs) {  return function partiallyApplied(...laterArgs) {    return fn(...presetArgs, ...laterArgs)  }}复制代码

使用

const getPerson = partial( ajax, "http://some.api/person" )const getOrder = partial( ajax, "http://some.api/order" )// 当然你可以写成 partial(ajax, 'http://xxx' { user: CURRENT_USRE_ID })// 但下面这种更合适一些const getCurrentUser = partial( getPerson, { user: CURRENT_USER_ID } )// 为方便理解,展开一下getCurrentUservar getCurrentUser = function outerPartiallyApplied(...outerLaterArgs){  var getPerson = function innerPartiallyApplied(...innerLaterArgs){    return ajax("http://some.api/person", ...innerLaterArgs);  }  return getPerson({ user: CURRENT_USER_ID }, ...outerLaterArgs)}复制代码

例2

function add(x, y) {  return x + y}// [11, 12, 13][1, 2, 3].map(function adder(val) {  return add(val + 10)})// 改用partial来将add函数适配map回调函数[1, 2, 3].map(partial(add, 10))复制代码

partialRight

如果上面的函数,我们预先知道的是data和callback, 而暂时不知道url呢?

版本一,使用前面的reverseArgs(反转参数)及partial

function partialRight(fn,...presetArgs) {  return reverseArgs(     partial( reverseArgs( fn ), ...presetArgs.reverse() )  )}// 使用function add(a, b, c, d) {  return a + b * 2 + c * 3 + d * 4}const add2 = partialRight(add, 30, 40)// 10 + 20 * 2 + 30 * 3 + 40 * 4 = 300add2(10, 20)// 理解partialRight// reverseArgs(fn)返回一个函数,调用fn时将参数反转let fn2 = function argsReversed(...args) {  return fn(...args.reverse())}// partial(reverseArgs(fn), ...presetArgs.reverse())的返回函数// 试想一下,调用p,会调用fn(...laterArgs.reverse(), ...presetArgs)// 所以需要对laterArgs也反转一次参数,fn(...lasterArgs, ...presetArgs)let p = function partiallyApplied(...laterArgs) {  return fn2(...presetArgs.reverse(), ...laterArgs)}复制代码

建议还是敲一下代码,或者在纸上写一下

版本二 实际上,版本一可以当个练习,帮助理解,其实可以更直接

// sq:function partialRight(fn, ...presetArgs) {  return function partiallyApplied(...laterArgs) {    return fn(...laterArgs, ...presetArgs)  }}复制代码

curry

curry即柯里化,将一个接收多个参数的函数分解一个连续的链式函数,其中每个函数接收一个参数而返回另一个函数接收下一个参数。(宽松型curry每个函数可以接收多个参数)

上例ajax, 如果使用curry

var personFetcher = curriedAjax( "http://some.api/person" )var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } )getCurrentUser( function foundUser(user){ /* .. */ } );复制代码

curry和partial很相似,但curry返回的函数,只能接收下一个参数

// sq:function curry(fn, arity = fn.length) {  return (function nextCurried(prevArgs) {    return function curried(nextArg) {      var args = [...prevArgs, nextArg]      if (args.length >= arity) {        return fn(...args)      } else {        return nextCurried(args)      }    }  })([])}复制代码

注意: 函数默认值形式,析构, 展开运算符形式...会导致fn.length不正确,所以此时应传入函数正确接收参数个数

之前的例2,使用curry

function add(x, y) {  return x + y}var adder = curry( add )[1, 2, 3].map(adder(10))复制代码

例3

function sum(...nums) {  var total = 0;  for (let num of nums) {    total += num;  }  return total;}// 15sum(1, 2, 3, 4, 5)var curriedSum = curry(sum, 5)// 15curriedSum(1)(2)(3)(4)(5)复制代码

实际上使用partial也是可以做到只接收一个参数,只需要一直对部分应用的函数手动连续调用partial,而curry则是自动。

function add(a, b, c) {  return a + b + c}const partial1 = partial(add, 1)const partial2 = partial(partial1, 2)const partial3 = partial(partial2, 3)// 6partial3()复制代码

参数传入次数太多有时也挺麻烦,所以也允许curry传入多个参数,大部分库也是这样做的

// sq:function looseCurry(fn, arity = fn.length) {  return (function nextCurried(prevArgs) {    return function curried(...nextArgs) {      var args = [...prevArgs, ...nextArgs];      if (args.length >= arity) {        return fn(...args);      } else {        return nextCurried(args);      }    };  })([]);}复制代码

当然curry也有curryRight,暂时不过多介绍,以后用到再写

小结

库的真实实现可能略有区别,因为它毕竟要考虑更多方法的通用作出更高层次的抽象,但是上面的代码已经很好的可以帮助我们理解partial和curry,从而窥见函数式的冰山一角

这里值得一提的是我们在partialRight版本一, 使用了.reverse(), 这个是数组的一个变异方法

let arr = [1, 2, 3]arr.reverse()// [3, 2, 1]console.log(arr)复制代码

而变异方法会导致函数"不纯",从而又要引出一个纯函数的概念,这个以后想到时会提及一些,不过想理解这个"纯"最好还是看看文章

至于代码风格,你要是更喜欢ES6的箭头函数,完全可以依据喜好,当然命名函数是有诸多好处的,比如可读性,调试等

参考

转载地址:http://pueql.baihongyu.com/

你可能感兴趣的文章
在Git中配置多ssh key登陆不同仓库
查看>>
java B2B2C springmvc mybatis多租户电子商城系统-服务网关过滤器
查看>>
从性能领先到体验领先,迅雷链带动区块链行业升级
查看>>
TiKV 源码解析系列文章(七)gRPC Server 的初始化和启动流程
查看>>
数据库-oracle-基础知识
查看>>
Drools workbench
查看>>
聚焦http协议缓存策略(RFC7234)在okhttp中的实现
查看>>
koa教程--busboy模块
查看>>
Android:四大架构的优缺点,你真的了解吗?
查看>>
互联网分布式微服务云平台规划分析--平台整体规划
查看>>
(八)Java B2B2C多用户商城 springcloud架构- commonservice-eureka 项目构建过程
查看>>
Java线程池Executor使用
查看>>
springmvc入门之映射处理器(二)
查看>>
Activity之间数据传递
查看>>
nagios分组出图代码实现讲解[1]
查看>>
我的友情链接
查看>>
mybatis错误2
查看>>
Win7+Ubuntu11
查看>>
apache服务器的fin_wait1过多time_wait过多问题解决
查看>>
1999年与GNU创始人Richard M. Stallman大神的合影
查看>>