函数式javascript_JavaScript函数式编程介绍:部分应用程序和库里

函数式javascript

介绍 (Introduction)

With the adoption of the Redux JavaScript library, the Reason syntax extension and toolchain, and the Cycle JavaScript framework, functional programming with JavaScript is becoming increasingly relevant. Two important ideas with roots in functional thought are currying, which transforms a function of multiple arguments into a series of function calls, and partial application, which fixes the value of some of a function’s arguments without fully evaluating the function. In this article, we will explore some examples of these ideas in action, as well as identify a few places they show up that might surprise you.

随着Redux JavaScript库, Reason语法扩展和工具链以及Cycle JavaScript框架的采用,使用JavaScript进行功能编程变得越来越重要。 根植于功能思想的两个重要思想是curring (将多个参数的功能转换为一系列函数调用)和部分应用 ( 部分应用) ,该应用在不完全评估功能的情况下固定某些函数参数的值。 在本文中,我们将探讨这些想法在实践中的一些示例,并确定它们出现的一些地方可能会让您感到惊讶。

After reading this, you’ll be able to:

阅读本文后,您将能够:

  • Define partial application and currying and explain the difference between the two.

    定义部分应用程序和currying并解释两者之间的区别。
  • Use partial application to fix arguments to a function.

    使用部分应用程序将参数固定到函数。
  • Curry functions to facilitate partial application.

    咖喱功能有助于部分应用。
  • Design functions that facilitate partial application.

    有助于部分应用的设计功能。

没有部分应用的示例 (Example without Partial Application)

As with many patterns, partial application is easier to understand with context.

与许多模式一样,局部应用更容易通过上下文理解。

Consider this buildUri function:

考虑以下buildUri函数:

function buildUri (scheme, domain, path) {
  return `${scheme}://${domain}/${path}`
}

We call like this:

我们这样称呼:

buildUri('https', 'twitter.com', 'favicon.ico')

This produces the string https://twitter.com/favicon.ico.

这将产生字符串https://twitter.com/favicon.ico

This is a handy function if you’re building lots of URLs. If you work mainly on the web, though, you’ll rarely use a scheme other than http or https:

如果您要构建大量URL,这是一个方便的功能。 但是,如果您主要在网络上工作,则很少使用httphttps以外的scheme

const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')
const googleHome = buildUri('https', 'google.com', '')

Note the commonality between these two lines: Both pass https as an initial argument. We’d prefer to cut out the repetition and write something more like:

请注意这两行之间的共同点:两者都将https作为初始参数传递。 我们希望减少重复并编写类似以下内容的内容:

const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')

There are a couple of ways to do this. Let’s see how to achieve it with partial application.

有两种方法可以做到这一点。 让我们看看如何通过部分应用程序实现它。

部分应用:修复参数 (Partial Application: Fixing Arguments)

We agreed that, instead of:

我们同意,而不是:

const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')

We’d prefer to write:

我们更愿意写:

const twitterFavicon =  buildHttpsUri('twitter.com', 'favicon.ico')

Conceptually, buildHttpsUri does exactly the same thing as buildUri, but with a fixed value for its scheme argument.

从概念上讲, buildHttpsUri完全一样的东西作为buildUri ,但其固定值scheme论证。

We could implement buildHttpsUri directly like so:

我们可以像这样直接实现buildHttpsUri

function buildHttpsUri (domain, path) {
  return `https://${domain}/${path}`
}

This will do what we we want, but has not yet solved our problem completely. We’re duplicating buildUri, but hard-coding https as its scheme argument.

这将完成我们想要的,但尚未完全解决我们的问题。 我们正在复制buildUri ,但是将https硬编码为其scheme参数。

Partial application lets us do this, but by taking advantage of the code we already have in buildUri . First, we’ll see how to do this using a functional utility library called Ramda. Then, we’ll try doing it by hand.

部分应用程序使我们可以执行此操作,但是可以利用buildUri已有的代码。 首先,我们将看到如何使用称为Ramda的功能实用程序库来执行此操作。 然后,我们将尝试手动进行。

使用Ramda (Using Ramda)

Using Ramda, partial application looks like this:

使用Ramda,部分应用程序如下所示:

// Assuming we're in a node environment
const R = require('ramda')

// R.partial returns a new function (!)
const buildHttpsUri = R.partial(buildUri, ['https'])

After that, we can do:

之后,我们可以执行以下操作:

const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')

Let’s break down what happened here:

让我们分解一下这里发生的事情:

  • We called Ramda’s partial function, and passed two arguments: First, a function, called buildUri, and second, an array containing one "https" value.

    我们调用了Ramda的partial函数,并传递了两个参数:首先是一个名为buildUri的函数,其次是一个包含一个"https"值的数组

  • Ramda then returns a new function, which behaves like buildUri, but with "https" as its first argument.

    Ramda然后返回一个新函数,其行为类似于buildUri ,但第一个参数为"https"

Passing more values in the array fixes further arguments:

在数组中传递更多值可修复更多参数:

// Bind `https` as first arg to `buildUri`, and `twitter.com` as second
const twitterPath = R.partial(buildUri, ['https', 'twitter.com'])

// Outputs: `https://twitter.com/favicon.ico`
const twitterFavicon = twitterPath('favicon.ico')

This allows us to reuse general code we’ve written elsewhere by configuring it for special cases.

通过配置特殊情况,我们可以重用我们在其他地方编写的通用代码。

手动部分申请 (Manual Partial Application)

In practice, you’ll use utilities like partial whenever you need to use partial application. But, for the sake of illustration, let’s try to do this ourselves.

在实践中,每当需要使用部分应用程序时,都将使用诸如partial实用程序。 但是,为了说明起见,让我们尝试自己进行此操作。

Let’s see the snippet first, and then dissect.

我们先来看一下代码片段,然后再进行剖析。

// Line 0
function fixUriScheme (scheme) {
  console.log(scheme)
  return function buildUriWithProvidedScheme (domain, path) {
    return buildUri(scheme, domain, path)
  }
}

// Line 1
const buildHttpsUri = fixUriScheme('https')

// Outputs: `https://twitter.com/favicon.ico`
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')

Let’s break down what happened.

让我们分解一下发生的事情。

  • On Line 0, we define a function called fixUriScheme. This function accepts a scheme, and returns another function.

    在第0行上,我们定义了一个名为fixUriScheme的函数。 该函数接受一个scheme ,并返回另一个函数。

  • On Line 1, we save the result of calling fixUriScheme('https') into a variable called buildHttpsUri, which behaves exactly the same as the version we built with Ramda.

    在第1行中,我们将调用fixUriScheme('https')的结果保存到名为buildHttpsUri的变量中,该变量的行为与我们使用buildHttpsUri构建的版本完全相同。

Our function fixUriScheme accepts a value, and returns a function. Recall that this makes it a higher-order function, or HOF. This returned function only accepts two arguments: domain and path.

我们的函数fixUriScheme接受一个值,并返回一个函数。 回想一下,这使它成为高阶函数或HOF。 此返回的函数仅接受两个参数: domainpath

Note that, when we call this returned function, we only explicitly pass domain and path, but it remembers the scheme we passed on Line 1. This is because the inner function, buildUriWithProvidedScheme, has access to all of the values in its parent function’s scope, even after the parent function has returned. This is what we call closure.

请注意,当我们调用此返回的函数时,我们仅显式传递domainpath ,但它记住在第1行传递的scheme 。这是因为内部函数buildUriWithProvidedScheme可以访问其父函数范围内的所有值,即使父函数已返回。 这就是我们所说的闭包

This generalizes. Any time a function returns another function, the returned function has access to any variables initialized within the parent function’s scope. This is a good example of using closure to encapsulate state.

这是普遍的。 每当函数返回另一个函数时,返回的函数都可以访问父函数范围内初始化的任何变量。 这是使用闭包封装状态的一个很好的例子。

We could do something similar using an object with methods:

我们可以使用带有方法的对象来做类似的事情:

class UriBuilder {

  constructor (scheme) {
    this.scheme = scheme
  }

  buildUri (domain, path) {
    return `${this.scheme}://${domain}/${path}`
  }
}

const httpsUriBuilder = new UriBuilder('https')

const twitterFavicon = httpsUriBuilder.buildUri('twitter.com', 'favicon.ico')

In this example, we configure each instance of the UriBuilder class with a specific scheme. Then, we can call the buildUri method, which combines the user’s desired domain and path with our pre-configured scheme to produce the desired URL.

在此示例中,我们使用特定的scheme配置UriBuilder类的每个实例。 然后,我们可以调用buildUri方法,该方法将用户所需的domainpath与我们的预配置scheme以生成所需的URL。

泛化 (Generalizing)

Recall the example we started with:

回顾我们开始的示例:

const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')

const googleHome = buildUri('https', 'google.com', '')

Let’s make a slight change:

让我们做些改动:

const twitterHome = buildUri('https', 'twitter.com', '')

const googleHome = buildUri('https', 'google.com', '')

This time there are two commonalities: The scheme, "https", in both cases, and the path, here the empty string.

这次有两个共同点:两种情况下的方案"https" ,以及路径(此处为空字符串)。

The partial function we saw earlier partially applies from the left. Ramda also provides partialRight, which allows us to partially apply from right to left.

我们前面看到的partial函数从左开始部分应用。 Ramda还提供了partialRight ,这使我们可以从右到左部分应用。

const buildHomeUrl = R.partialRight(buildUri, [''])

const twitterHome = buildHomeUrl('https', 'twitter.com')
const googleHome = buildHomeUrl('https', 'google.com')

We can take this further:

我们可以进一步:

const buildHttpsHomeUrl = R.partial(buildHomeUrl, ['https'])

const twitterHome = buildHttpsHomeUrl('twitter.com')
const googleHome = buildHttpsHomeUrl('google.com')

设计考虑 (A Design Consideration)

To fix both the scheme and path arguments to buildUrl, we had to first use partialRight, and then use partial on the result.

为了将schemepath参数都修改为buildUrl ,我们必须首先使用partialRight ,然后对结果使用partial

This isn’t ideal. It would be better if we could use partial (or partialRight), instead of both in sequence.

这不理想。 如果我们可以使用partial (或partialRight ),而不是partialRight使用两者,那会更好。

Let’s see if we can fix this. If we redefine buildUrl:

让我们看看是否可以解决此问题。 如果我们重新定义buildUrl

function buildUrl (scheme, path, domain) {
  return `${scheme}://${domain}/${path}`
}

This new version passes the values we are likely to know upfront first. The last argument, domain, is the one we’re most likely to want to vary. Arranging arguments in this order is a good rule of thumb.

这个新版本传递了我们可能首先要知道的值。 最后一个参数domain ,是我们最想改变的参数。 按此顺序排列参数是一个很好的经验法则。

We can also use only partial:

我们也可以只使用partial

const buildHttpsHomeUrl = R.partial(buildUrl, ['https', ''])

This drives home the point that argument order matters. Some orders are more convenient for partial application than others. Take time to think about argument order if you plan to use your functions with partial application.

这使论据顺序很重要。 有些订单比其他订单更便于部分应用。 如果您打算将函数与部分应用程序一起使用,请花一些时间考虑参数顺序。

咖喱和方便的局部应用 (Currying and Convenient Partial Application)

We’ve now redefined buildUrl with a different argument order:

现在,我们以不同的参数顺序重新定义了buildUrl

function buildUrl (scheme, path, domain) {

  return `${scheme}://${domain}/${path}`

}

Note that:

注意:

  • The arguments we are most likely to want to fix appear on the left. The one we want to vary is all the way on the right.

    我们最有可能要修正的参数出现在左侧。 我们想要改变的就是一路在右边。
  • buildUri is a function of three arguments. In other words, we need to pass three things to get it to run.

    buildUri是三个参数的函数。 换句话说,我们需要传递三件事来使其运行。

There’s a strategy we can use to take advantage of this:

有一种我们可以利用的策略:

const curriedBuildUrl = R.curry(buildUrl)

// We can fix the first argument...
const buildHttpsUrl = curriedBuildUrl('https')
const twitterFavicon = buildHttpsUrl('twitter.com', 'favicon.ico')

// ...Or fix both the first and second arguments...
const buildHomeHttpsUrl = curriedBuildUrl('https', '')
const twitterHome = buildHomeHttpsUrl('twitter.com')

// ...Or, pass everything all at once, if we have it
const httpTwitterFavicon = curriedBuildUrl('http', 'favicon.ico', 'twitter.com')

The curry function takes a function, curries it, and returns a new function, not unlike partial.

curry函数接受一个函数,对其进行咖喱处理,然后返回一个新函数,与partial不同。

Currying is the process of transforming a function that we call all at once with multiple variables, like buildUrl, into a series of function calls, where we pass each variable one at a time.

Currying是将一个带有多个变量的函数(例如buildUrl )一次调用的函数转换为一系列函数调用的过程,在该函数中,我们一次传递每个变量。

  • curry doesn’t fix arguments immediately. The returned function takes as many arguments as the original function.

    curry不会立即解决参数。 返回的函数接受的参数与原始函数一样多。

  • If you pass all the necessary arguments to the curried function, it will behave like buildUri.

    如果将所有必要的参数传递给buildUri函数,则其行为将类似于buildUri

  • If you pass fewer arguments than the original function took, the curried function will automatically return the same thing you’d get by calling partial.

    如果传递的参数少于原始函数传递的参数,那么curried函数将通过调用partial自动返回与所得到的相同的结果。

Currying gives us the best of both worlds: Automatic partial application and the ability to use our original functions.

Currying为我们提供了两全其美的优势:自动部分应用程序以及使用我们原始功能的能力。

Note that currying makes it easier to create partially applied versions of our functions. This is because curried functions are convenient to partially apply, as long as we’ve been careful about our argument ordering.

请注意,使用currying可以更轻松地创建函数的部分应用版本。 这是因为只要我们对参数顺序保持谨慎,就可以方便地部分使用咖喱函数。

We can call curriedbuildUrl the same way we’d call buildUri:

我们可以像调用curriedbuildUrl一样调用buildUri

const curriedBuildUrl = R.curry(buildUrl)

// Outputs: `https://twitter.com/favicon.ico`
curriedBuildUrl('https', 'favicon.ico', 'twitter.com')

We can also call it like this:

我们也可以这样称呼它:

curriedBuildUrl('https')('favicon.ico')('twitter.com')

Note that that curriedBuildUrl('https') returns a function, which behaves like buildUrl, but with its scheme fixed to "https" .

请注意, curriedBuildUrl('https')返回一个函数,该函数的行为类似于buildUrl ,但其方案固定为"https"

Then, we immediately call this function with "favicon.ico". This returns another function, which behaves like buildUrl, but with its scheme fixed to"https" and its path fixed to the empty string.

然后,我们立即使用"favicon.ico"调用此函数。 这将返回另一个函数,其行为类似于buildUrl ,但其方案固定为"https" ,其路径固定为空字符串。

Finally, we invoke this function with "twitter.com". Since this is the last argument, the function resolves to the final value of: http://twitter.com/favicon.ico.

最后,我们使用"twitter.com"调用此函数。 由于这是最后一个参数,因此该函数将解析为最终值: http://twitter.com/favicon.ico : http://twitter.com/favicon.ico

The important takeaway is: curriedBuldUrl can be called as a sequence of function calls, where we pass only one argument with each call. It is the process of converting a function of many variables passed “all at once” into such a sequence of “one-argument calls” that we call currying.

重要的curriedBuldUrl是: curriedBuldUrl可以作为函数调用序列来调用,其中每次调用仅传递一个参数。 这是将许多“一次全部”传递的变量的函数转换为我们称为“ curring”的“单参数调用”序列的过程。

结论 (Conclusion)

Let’s recap the major takeaways:

让我们回顾一下主要内容:

  • Partial application allows us to fix a function’s arguments. This lets us derive new functions, with specific behavior, from other, more general functions.

    部分应用程序使我们可以修复函数的参数。 这使我们能够从其他更通用的功能中衍生出具有特定行为的新功能。

  • Currying transforms a function that accepts multiple arguments “all at once” into a series of function calls, each of which involves only one argument at a time. Curried functions with a well-designed argument order are convenient to partially apply.

    Currying将“一次全部”接受多个参数的函数转换为一系列函数调用,每个函数调用一次仅涉及一个参数。 设计良好的参数顺序的库里函数便于部分应用。

  • Ramda provides partial, partialRight, and curry utilities. Similar popular libraries include Underscore and Lodash.

    Ramda提供partialpartialRight ,和curry实用程序。 类似流行的库包括下划线Lodash

翻译自: https://www.digitalocean.com/community/tutorials/javascript-functional-programming-explained-partial-application-and-currying

函数式javascript

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值