纯函数
以下内容摘自Reversi 黑白棋的前端界面与 AI 实现 | OrangeX4’s Blog
函数式编程有一个很重要的原则: 尽量使用纯函数 (pure function).
什么是纯函数? 纯函数就是, 给定相同的输入, 一定有相同的输出, 且没有任何副作用的函数. 数学函数就是真正的纯函数, 如三角函数 sin, 给定相同的输入 x, 一定会有相同的输出 y.
纯函数有什么好处呢? 可控, 没有副作用, 支持高并发, 可以很方便地缓存.
支持高并发是因为, 每个用户均有自己的数据, 他们不需要争夺对同一个数据的使用和修改权, 所以可以很方便地将纯函数部署到不同的服务器上, 而不用担心资源共享, 服务器通信和资源锁之类的麻烦问题.
而缓存就更为简单了, 这是一种用空间换时间的策略. 对于运算复杂耗时长的纯函数来说, 我们只需要, 在每一次运行这个函数时, 把 输入到输出的映射 记录在一个哈希表中, 下一次运行这个函数的时候, 可以先看看这个输入在不在哈希表中, 在的话, 直接使用表中的数据, 这就是缓存.
缓存这个思路可运用的范围很广。
我还可以举一个例子,文生图网站的默认prompt,大部分新进用户如果想要尝试,很大可能会先直接使用默认参数进行生成,那么这默认的参数所生成的图片就可以缓存,后端直接发过去就是,而不用再生成。如果想更进一步节省成本,那可以将用户提供的参数进行hash,命中了就发对应的图片。
不少商用的大语言模型也提供了类似的选项。它会提供给用户是否使用缓存的选项,如果允许并且成功命中缓存,那么所需费用能大大节省。
偏函数
wip
柯里化(curry)
- 把接收多参的函数转化成可以逐个调用单个参数并返回接收剩下参数的函数
- JS实现
function curry(func) { return function curried(...args){ if args.length >= func.length{ // func.length:func的参数的数量 // 如果传入的 args 长度与原始函数所定义的(func.length)相同或者更长 return func.apply(this,args); } else{ return function(...args2){ // 重新应用 curried,将之前传入的参数与新的参数一起传入。 return curried.apply(this,args.concat(args2)); } } } }
- 上述代码的限制
- 函数具有固定数量的参数
- 使用 rest 参数的函数,例如 f(…args),不能以这种方式进行柯里化
- 另一种实现
function curry(fn,..bindArgs){ return (...args)=>{ const allArgs = [...bindArgs,...args]; if(allArgs.length<fn.length){ // func.length:func的参数的数量 // 如果传入的 args 长度与原始函数所定义的(func.length)相同或者更长 return curry(fn,...allArgs); }else{ return fn(...allArgs); } } }
- 实际使用:lodash 库的 _.curry
- 参考:柯里化(Currying)
组合
- 示例
// 这里的withOne等函数应为纯函数 const X1 = withOne(X); const X2 = withTwo(X1); const X3 = withThree(X2); const SuperX = X3; //最终的SuperX具备三个高阶组件的超能力 // 等价于 const SuperX = withThree(withTwo(withOne(X))); // 等价于 const hoc = compose(withThree, withTwo, withOne); const SuperX = hoc(X);
- 参考实现
export default 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)))
}