介绍
随着 Redux JavaScript 库的采用, Reason语法扩展和工具链,以及 Cycle JavaScript 框架,使用 JavaScript 的功能编程变得越来越重要。在功能思维中有根源的两个重要想法是 currying,它将多个论点的函数转化为一系列函数调用,以及 partial application,它可以固定函数的某些论点的价值,而不完全评估函数。
读完这篇文章后,你将能够:
- 定义部分应用和库里,并解释两者之间的区别
- 使用部分应用来对函数进行定义
- 库里函数来促进部分应用
- 设计函数来促进部分应用
没有部分应用的例子
与许多模式一样,部分应用更容易理解。
考虑这个buildUri
函数:
1function buildUri (scheme, domain, path) {
2 return `${scheme}://${domain}/${path}`
3}
我们这样称呼:
1buildUri('https', 'twitter.com', 'favicon.ico')
这会产生字符串 https://twitter.com/favicon.ico
。
但是,如果你主要在网络上工作,你很少使用方案
,而不是http
或https
:
1const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')
2const googleHome = buildUri('https', 'google.com', '')
注意这两个行之间的共同之处: 两者都通过https
作为初始论点. 我们更愿意切断重复并写一些更像这样的东西:
1const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
有几种方法可以做到这一点,让我们看看如何通过部分应用来实现。
部分应用:固定论点
我们同意这一点,而不是:
1const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')
我们宁愿写:
1const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
从概念上讲,buildHttpsUri 与buildUri 完全相同,但其方案
参数具有固定的值。
我们可以直接实现buildHttpsUri
,如下:
1function buildHttpsUri (domain, path) {
2 return `https://${domain}/${path}`
3}
这将做我们想要的,但还没有完全解决我们的问题. 我们重复了buildUri
,但硬编码了https
作为它的方案
论点。
部分应用允许我们做到这一点,但通过利用我们在buildUri中已经拥有的代码。首先,我们将看到如何使用一个名为Ramda(LINK0)的功能工具库来做到这一点。
使用 Ramda
使用 Ramda,部分应用程序看起来像这样:
1// Assuming we're in a node environment
2const R = require('ramda')
3
4// R.partial returns a new function (!)
5const buildHttpsUri = R.partial(buildUri, ['https'])
之后,我们可以做:
1const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
让我们把这里发生的事情分解下来:
- 我们调用了Ramda的
部分
函数,并通过了两个参数:第一,一个函数,称为buildUri
,第二,一个包含一个https
值的 array。
在数组中传递更多值可修复进一步的参数:
1// Bind `https` as first arg to `buildUri`, and `twitter.com` as second
2const twitterPath = R.partial(buildUri, ['https', 'twitter.com'])
3
4// Outputs: `https://twitter.com/favicon.ico`
5const twitterFavicon = twitterPath('favicon.ico')
这使我们能够重复使用我们在其他地方编写的通用代码,将其配置为特殊案例。
手动部分应用
在实践中,当你需要使用部分应用时,你会使用部分
这样的实用工具,但为了说明,让我们试着自己做。
让我们先看看片段,然后分解。
1// Line 0
2function fixUriScheme (scheme) {
3 console.log(scheme)
4 return function buildUriWithProvidedScheme (domain, path) {
5 return buildUri(scheme, domain, path)
6 }
7}
8
9// Line 1
10const buildHttpsUri = fixUriScheme('https')
11
12// Outputs: `https://twitter.com/favicon.ico`
13const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
让我们打破发生了什么。
- 在 0 行,我们定义了一个名为
fixUriScheme
的函数. 这个函数接受一个scheme
,并返回另一个函数. - 在 1 行,我们将调用
fixUriScheme('https')
的结果保存到一个名为buildHttpsUri
的变量中,该函数与我们用 Ramda 构建的版本完全相同
我们的函数 fixUriScheme
接受一个值,然后返回一个函数. 请记住,这使得它成为一个 高级函数,或 HOF. 这个返回的函数只接受两个参数: 域
和 路径
。
请注意,当我们称呼这个返回函数时,我们只会明确地传递域
和路径
,但它会记住我们在线上传递的方案
,这是因为内部函数buildUriWithProvidedScheme
可以访问其母函数范围内的所有值,即使在母函数返回后。
每当一个函数返回另一个函数时,返回的函数可以访问在母函数范围内初始化的任何变量。
我们可以使用一种用方法的对象做类似的事情:
1class UriBuilder {
2
3 constructor (scheme) {
4 this.scheme = scheme
5 }
6
7 buildUri (domain, path) {
8 return `${this.scheme}://${domain}/${path}`
9 }
10}
11
12const httpsUriBuilder = new UriBuilder('https')
13
14const twitterFavicon = httpsUriBuilder.buildUri('twitter.com', 'favicon.ico')
在本示例中,我们将UriBuilder
类的每个实例配置为一个特定的方案
,然后,我们可以调用buildUri
方法,该方法将用户所需的域
和路径
与我们预先配置的方案
相结合,以产生所需的URL。
一般化
回想一下我们开始的例子:
1const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')
2
3const googleHome = buildUri('https', 'google.com', '')
让我们来做一个小小的改变:
1const twitterHome = buildUri('https', 'twitter.com', '')
2
3const googleHome = buildUri('https', 'google.com', '')
这次有两个共同点:方案,https
在两种情况下,以及路径,这里是空串。
我們之前看到的「部分」函數部分適用於左側. Ramda 還提供 partialRight,讓我們可以部分適用於右側。
1const buildHomeUrl = R.partialRight(buildUri, [''])
2
3const twitterHome = buildHomeUrl('https', 'twitter.com')
4const googleHome = buildHomeUrl('https', 'google.com')
我们可以进一步采取这一点:
1const buildHttpsHomeUrl = R.partial(buildHomeUrl, ['https'])
2
3const twitterHome = buildHttpsHomeUrl('twitter.com')
4const googleHome = buildHttpsHomeUrl('google.com')
设计考虑
要将方案
和路径
论点都修复为buildUrl
,我们必须先使用partialRight
,然后在结果上使用partial
。
最好是我们可以使用部分
(或部分权利
),而不是连续使用两者。
让我们看看我们能否修复这个问题,如果我们重新定义‘buildUrl’:
1function buildUrl (scheme, path, domain) {
2 return `${scheme}://${domain}/${path}`
3}
这个新版本通过了我们最有可能提前知道的值。最后一个参数,‘域’,是我们最有可能想要改变的参数。
我们也可以使用部分
:
1const buildHttpsHomeUrl = R.partial(buildUrl, ['https', ''])
这引导回归论点,论点顺序重要. 某些命令比其他命令更方便的部分应用. 花时间考虑论点顺序,如果你打算使用你的函数与部分应用。
和方便的部分应用
现在我们重新定义了「buildUrl」以不同的参数顺序:
1function buildUrl (scheme, path, domain) {
2
3 return `${scheme}://${domain}/${path}`
4
5}
注意这:
- 我们最有可能想要修复的论点出现在左边,我们想要改变的论点是右边
buildUri
是由三个论点组成的函数,换句话说,我们需要通过三件事才能使它运行
有一个策略,我们可以利用这一点:
1const curriedBuildUrl = R.curry(buildUrl)
2
3// We can fix the first argument...
4const buildHttpsUrl = curriedBuildUrl('https')
5const twitterFavicon = buildHttpsUrl('twitter.com', 'favicon.ico')
6
7// ...Or fix both the first and second arguments...
8const buildHomeHttpsUrl = curriedBuildUrl('https', '')
9const twitterHome = buildHomeHttpsUrl('twitter.com')
10
11// ...Or, pass everything all at once, if we have it
12const httpTwitterFavicon = curriedBuildUrl('http', 'favicon.ico', 'twitter.com')
curry函数采取一个函数, curries 它,并返回一个新的函数,而不是部分
。
_Currying 是将一个函数,我们同时称之为多个变量,如buildUrl,转化为一系列函数调用,我们一次传递每个变量。
返回的函数需要和原始函数一样多的参数
- 如果您将所有必要的参数传递给 curried 函数,它将表现为
buildUri
- 如果您传递的参数比原始函数少, curried 函数将自动返回您通过调用
partial
获得的相同的东西。
Currying给了我们两个世界的最好:自动部分应用和使用我们的原始功能的能力。
请注意,currying 使创建我们函数的部分应用版本更容易,这是因为 curried 函数方便地部分应用,只要我们小心我们的论点排序。
我们可以像我们称之为buildUri那样称呼curriedbuildUrl:
1const curriedBuildUrl = R.curry(buildUrl)
2
3// Outputs: `https://twitter.com/favicon.ico`
4curriedBuildUrl('https', 'favicon.ico', 'twitter.com')
我们也可以这样称呼它:
1curriedBuildUrl('https')('favicon.ico')('twitter.com')
请注意,curriedBuildUrl
(https)返回一个函数,该函数行为像
buildUrl,但其方案固定为
https`。
然后,我们立即将这个函数称为favicon.ico
。这将返回另一个函数,该函数行为像buildUrl
,但其方案固定为https
,其路径固定为空串。
最后,我们用twitter.com
调用这个函数,因为这是最后一个参数,所以该函数解决到最后一个值:http://twitter.com/favicon.ico。
curriedBuldUrl
可以称为函数呼叫的序列,其中我们每次呼叫只传递一个论点,这是将许多变量的函数转换为一次
到我们称之为currying的一个论点呼叫
的序列的过程。
结论
让我们回顾一下主要的 takeaways:
- 部分应用允许我们修复函数的论点。这使我们能够从其他更一般的函数中提取新的函数,具有特定的行为
- Currying将一函数转化为一系列函数调用,每个函数只涉及一次一个论点。具有精心设计的论点顺序的学术函数方便地部分应用
- Ramda提供
部分
、partialRight
和curry
实用程序。