函数式编程(四):柯里化的威力
在前,讨论了函数式编程的基础和核心以及纯函数的特征。这篇文章将探讨函数的柯里化。
什么是柯里化
柯里化的函数是,在传递完所有的参数之前,一直返回函数的函数。
柯里化的工作原理
假设我们有add
函数
const add = (a, b) => a + b
最简单柯里化实现是使函数返回函数,如:
const add = (a) => (b) => a + b
使用方法如下:
const addOne = add(1) // addOne = (b) => 1 + b
另一种实现,假设,我们有一个 curry
函数,它能接受一个函数作为参数,然后柯里化它:
const add = curry((a, b) => a + b)
正如我们所看到的,curry
是一个函数,它使用另一个函数来延迟参数。我们可以这样调用它:
const addOne = add(1) // addOne = (b) => 1 + b
首先,我们通过将1作为第一个参数传递给柯里化的add
函数来创建addOne
。这产生了另一个函数,它等待传递其余的参数,在传递所有参数之前,不会执行add逻辑。
addOne(2) // 3
将2(作为b)传递给addOne;执行逻辑1+2。
快速总结:curry
函数接受一个函数并使其参数变为惰性。
为什么要柯里化
柯里化将使我们的代码:
-
更加的干净 -
更少的重复参数传递和更少的冗长代码 -
组合性更好 -
重复性更好
为什么柯里化能让我们的代码更好
主要是,一些函数将“配置”数据作为输入
如果我们有接受“config”参数的函数,我们最好将其柯里化,因为这些“config”参数可能会被反复使用。例如,假设我们有一个 translator
函数,它接受locale
和要翻译的text
文本:
const translator = (locale, text) => {/*translation*/}
用法如下:
translator('fr', 'Hello')
translator('fr', 'Goodbye')
translator('fr', 'How are you?')
每次我们调用translator
时,我们都应该传递locale
和 text
。这是多余且不干净的,在每次调用中都会传递locale
。
我们可以柯里化translator
函数:
const translator = curry((locale, text) => {/*translation*/})
const inFrench = translator('fr')
现在,inFrench
将fr
作为语言环境传递给柯里化的translator函数,并等待提供text
。我们可以这样使用它:
inFrench('Hello')
inFrench('Goodbye')
inFrench('How are you?')
柯里化确实帮一个大忙,我们不需要每次都指定区域设置,因为柯里化,inFrench
具有区域设置了。
在柯里化之后——在这个具体的例子中。代码变得:
-
更加干净了 -
不那么冗长,不那么多余
因为我们把“配置”和实际的“数据”分开了。这在许多情况下都非常方便。
在实际工作中
在实践中,我们有动态语言环境(每个用户都有不同的语言),可能是fr、en、de或其他。因此,我们最好将inFrench
重命名为translate
,translate
可以加载任何语言环境。现在我们有了一个translator
,它将区域设置作为“配置”,将text
作为数据。由于translator是柯里化的,所以我们能够将“config”和“data”参数分开。
为什么要将“配置”和“数据”参数分开?
许多组件和函数需要使用一些功能,但不应该或不能知道“配置”部分的数据。这些组件或函数只有“数据”部分。因此,这些函数将能够在不需要了解“配置”部分的情况下使用函数。因此,该组件或功能将更少地与系统耦合,这将使组件更具可组合性和可维护性。
我们什么时候使用柯里化
当我们知道函数中有“config”和“data”时,我们最好用柯里化。柯里化可以让我们把它们分开。这是一个成熟系统设计的标志。因为代码质量的一大支柱是关注点分离。即使一个函数需要所有参数才能正常运行,我们仍然更清楚何时传递参数以及在程序的哪一层上传递参数。
闭包和柯里化的关系
闭包:是由“父”函数返回的函数,可以访问父函数的内部状态。
柯里化:总是会导致闭包。因为柯里化函数返回的每个函数都会提供父函数的内部状态。
更多的例子
现在我们可以看一些有意义的例子…
例1️:
给定一个数字列表,将所有数字递增1
输入:[1,2,3,4,5]
输出:[2,3,4,5,6]
解决方案:
// the curried `add` function that we defined earlier
const addOne = add(1)
const incrementNumbers = map(addOne)
const incrementedNumbers = incrementNumbers(numbers)
例2️:
给定一个字符串,保留所有以“C”字母开头的单词
输入:“currying is awesome”
输出:“currying”
解决方案:
const startsWithC = startsWith('c')
const filterStartsWithC = filter(startsWithC)
const filteredWords = filterStartsWithC(words)
记住,()=>()=>。。。是柯里化的另一种实现,是柯里化的简单版本。
结论
柯里化只是让参数变得惰性化。如果函数一直返回函数,直到其所有参数都满足为止,那么它执行逻辑。我们还通过实际例子看到了它如何使我们的代码更干净、更简洁、更可组合、甚至更可重用。这就利用了关注点分离原则。
❝参考:https://blog.bitsrc.io/functional-programming-part-3-the-powers-of-currying-213eb69b234b
❞