柯里化入门:拆拆拆,拆出函数式编程的优雅

💡什么是柯里化?

想象你在做饭,需要盐、酱油、葱花这些配料。普通函数就像一次性把所有材料都丢进锅里;而柯里化更像“先放盐,尝一口;再放酱油,尝一口;最后放葱花”。它把原本需要多个参数的函数,拆解成一个接一个的单参数函数,每次只处理一个参数,直到所有材料(参数)都齐了,才开始正式“开煮”。


👨‍🔬柯里化的起源

柯里化这个名字,来自逻辑学家 Haskell Curry,他的研究让函数式编程有了坚实的理论地基。现在,在像 Haskell、Scala 这样的函数式编程语言里,柯里化几乎是日常操作;在 JavaScript 中,它同样被用来写出更优雅、可复用、可组合的代码。


🚀为什么要学柯里化?

  1. 参数复用:先给函数“喂”一部分参数,返回的新函数可以在需要时继续使用。

  2. 延迟执行:函数可以只配置参数,不立即执行,直到真正需要的时候才调用。

  3. 函数组合:把多个小函数像乐高一样组合起来,形成强大的功能模块。


💻实际应用场景

无论是优化 Redux 的 Selector,处理复杂数据流,还是对深度嵌套函数进行优雅封装,柯里化都能让代码变得更灵活、更清晰、更优雅。

🍔 例子:用柯里化制作汉堡

假设你在做汉堡,需要选择面包、肉饼、酱料三个部分:

function makeBurger(bread, patty, sauce) {
  return `你的汉堡:${bread} + ${patty} + ${sauce}`;
}

如果使用柯里化:

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...nextArgs) {
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}

const curriedMakeBurger = curry(makeBurger);

console.log(curriedMakeBurger('芝麻面包')('牛肉饼')('番茄酱'));
// 输出:你的汉堡:芝麻面包 + 牛肉饼 + 番茄酱

🍟 更有趣的调用方式

你甚至可以先选好面包,后面再慢慢决定其他配料:

const withBread = curriedMakeBurger('全麦面包');
const withPatty = withBread('鸡肉饼');
console.log(withPatty('黑椒酱'));
// 输出:你的汉堡:全麦面包 + 鸡肉饼 + 黑椒酱

🔧 为什么这很酷?

  • 参数固定:你可以先固定常用参数(例如固定成“全麦面包+牛肉饼”的健康汉堡)。

  • 分步配置:根据用户选择一步步生成,常用于表单、多步骤配置页面。

  • 可组合:结合其他函数式工具,如 compose、pipe,构建强大的数据处理流。

👨‍🔧 1. 从零实现柯里化

让我们写一个万能 curry 函数,把任何多参数函数,变成可以逐步调用的“厨师函数”。

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      // 参数够了,就开饭
      return fn.apply(this, args);
    } else {
      // 参数不够,再返回一个函数接着收
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

🔬 2. 它是怎么工作的?

  1. curry 接收目标函数(比如做饭函数)

  2. curried 函数检测参数数量:够就执行,不够就继续收集

  3. 递归调用:不停返回新函数,直到收齐全部参数

  4. 执行目标函数,返回结果

💻 3. 测试:乘法示例

function multiply(a, b, c) {
  return a * b * c;
}

const curriedMultiply = curry(multiply);

console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2)(3, 4)); // 24

🎉 多灵活! 你可以一步步传,也可以分批传,最后总会算出结果。

🌈 4. 柯里化的优点

(1) 参数复用

先固定一部分参数,像提前腌好的鸡肉,随时可用。

function greet(greeting, name) {
  return `${greeting}, ${name}!`;
}

const sayHello = curry(greet)("Hello");
console.log(sayHello("Alice")); // Hello, Alice!
console.log(sayHello("Bob"));   // Hello, Bob!


(2) 延迟执行

柯里化可以“先接收参数,后执行”,就像提前预约:

const add = curry((a, b) => a + b);
const addFive = add(5);
console.log(addFive(10)); // 15

(3) 函数组合

把多个函数组合成数据处理管道,逻辑清晰,读起来就像在看流水线

const compose = (f, g) => (x) => f(g(x));
const addOne = (x) => x + 1;
const double = (x) => x * 2;

const addOneThenDouble = compose(double, addOne);
console.log(addOneThenDouble(3)); // 8


💡 5. 为什么柯里化这么强大?

它的核心理念就是:

  • 一步做一件事,函数变得简单、纯粹、好测试。

  • 没有副作用,只依赖参数,不影响全局,代码更安全可靠。


🚀 6. 实际应用场景

🛒 (1) Redux Selector 优化

在 Redux 的 reselect 中,柯里化用于高性能 Selector。

const selectCart = (state) => state.cart;
const selectCartItems = createSelector(
  selectCart,
  (cart) => cart.items
);

const selectCartTotal = createSelector(
  selectCartItems,
  (items) => items.reduce((total, item) => total + item.price * item.quantity, 0)
);

console.log(`Cart Total: $${selectCartTotal(state)}`);

🌟 好处:避免重复计算,让状态派生更快。


💬 (2) 数据管道:敏感词过滤 + 格式化 + 统计字数
const filterSensitiveWords = curry((words, text) => 
  words.reduce((acc, word) => acc.replace(new RegExp(word, 'gi'), '***'), text)
);
const formatText = (text) => text.trim().toLowerCase();
const countWords = (text) => text.split(/\s+/).length;

const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x);

const processTextPipeline = compose(
  countWords,
  formatText,
  filterSensitiveWords(['badword', 'offensive'])
);

const result = processTextPipeline(" This is a BadWord example! Badword is offensive. ");
console.log(result); // 输出处理后的字数

🔗 (3) 深度嵌套函数优化:带 Token 的 fetch
const fetchWithAuth = curry((authToken, endpoint) => 
  fetch(endpoint, { headers: { Authorization: `Bearer ${authToken}` } })
);

const fetchUserData = fetchWithAuth('my-secure-token');

Promise.all([
  fetchUserData('/api/user/1'),
  fetchUserData('/api/user/2')
])
  .then(([user1, user2]) => Promise.all([user1.json(), user2.json()]))
  .then(console.log)
  .catch(console.error);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老罗技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值