vlambda博客
学习文章列表

函数式编程(四):柯里化的威力

在前,讨论了函数式编程的基础和核心以及纯函数的特征。这篇文章将探讨函数的柯里化。

什么是柯里化

柯里化的函数是,在传递完所有的参数之前,一直返回函数的函数。

柯里化的工作原理

假设我们有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函数接受一个函数并使其参数变为惰性。

为什么要柯里化

柯里化将使我们的代码:

  1. 更加的干净
  2. 更少的重复参数传递和更少的冗长代码
  3. 组合性更好
  4. 重复性更好

为什么柯里化能让我们的代码更好

主要是,一些函数将“配置”数据作为输入
如果我们有接受“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'

现在,inFrenchfr作为语言环境传递给柯里化的translator函数,并等待提供text。我们可以这样使用它:

inFrench('Hello')
inFrench('Goodbye')
inFrench('How are you?')

柯里化确实帮一个大忙,我们不需要每次都指定区域设置,因为柯里化,inFrench具有区域设置了。

在柯里化之后——在这个具体的例子中。代码变得:

  1. 更加干净了
  2. 不那么冗长,不那么多余
    因为我们把“配置”和实际的“数据”分开了。这在许多情况下都非常方便。

在实际工作中

在实践中,我们有动态语言环境(每个用户都有不同的语言),可能是fr、en、de或其他。因此,我们最好将inFrench重命名为translatetranslate可以加载任何语言环境。现在我们有了一个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


少爷,公主,点个关注和在看呗!