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

javascript函数式

Summary: Currying transforms a function of multiple arguments to a series of function calls, each of which involves just one of those arguments. Partial application fixes the value of some of a function's arguments without fully evaluating the function.

简介Currying将多个参数的功能转换为一系列函数调用,每个函数调用仅涉及其中一个参数。 部分应用程序在不完全评估函数的情况下固定了函数某些参数的值。

Redux. Reason. Cycle.

Redux原因循环

Aside from kick-ass names, what these all have in common is functional flavor. Reason is a syntactically C-like dialect of [OCaml]; Redux was inspired by the classically functional Elm Architecture; and Cycle adopts a source/sink architecture that facilitates clean separation of pure and impure elements of an application

除了踢屁股的名字外,这些名字的共同点是功能风味。 理性是[OCaml]的句法似C的方言。 Redux的灵感来自具有古典功能的Elm建筑 ; and Cycle采用源/接收器架构,可促进应用程序纯净元素和不纯元素的清晰分离

Given the names and adoption rates behind these ecosystems, the verdict is clear:

鉴于这些生态系统背后的名称和采用率,结论很明确:

Familiarity with functional programming is non-negotiable for serious programmers.

对于认真的程序员来说,不熟悉函数式编程是不可以的。

Two important ideas with roots in functional thought are currying and partial application. Today, I'd like to explore some examples of these ideas in action, as well as identify a few places they show up that might surprise you.

起源于功能思想的两个重要思想是递归局部应用 。 今天,我想探讨这些想法在实践中的一些例子,并找出它们可能会让您感到惊讶的几个地方。

After reading this, you'll be able to:

阅读本文后,您将能够:

  • Define partial application and currying, and explain how they're different 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-.

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

As prerequisite, I assume you're familiar with closure; higher-order functions (HOFs); and map.

作为前提,我假设您熟悉闭包高阶函数 (HOF); 和地图

Let's get to it.

让我们开始吧。

令人烦恼的烦恼 ( A Motivating Annoyance )

As with all patterns, partial application is easier to understand with a motivating 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')

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

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

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. Life would be easier, and more elegant, if we didn't repeat ourselves. We'd prefer to 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}`
}

...And it would do precisely what we want.

...而这正是我们想要的。

But this should feel suspicious. We're essentially duplicating buildUri, but hard-coding https as its scheme argument. These are both code smells.

但这应该令人怀疑。 我们本质上是在复制buildUri ,但将https硬编码为其scheme参数。 这些都是代码的味道。

Partial application lets us do exactly 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 have a crack at doing it by hand.

部分应用程序使我们能够做到这一点,但是通过利用buildUri已经拥有的代码。 首先,我们将看到如何使用称为Ramda的功能实用程序库来执行此操作。 然后,我们将需要手工进行破解。

使用Ramda (Using Ramda)

Using Ramda, partial application looks like this:

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

// Assuming we're in a node enironment
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')

...As desired. Let's break down what happened, here.

...如预期的。 让我们在这里分解发生的事情。

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

    我们调用了Ramda的partial函数,并传递了两个参数:首先,一个名为buildUri的函数; 第二个是包含一个值的数组"https" ..
  2. 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')

The most obvious reason this is powerful is that it 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.

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

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

    在第0行上,我们定义了一个名为fixUriScheme的函数。 该函数接受一个scheme ,并返回另一个函数
  2. 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 one 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')

This is probably more familiar for most. 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。

Both approaches are just as good. Which is "better" depends on context: Your team, your project, and your preferences.

两种方法都一样好。 哪个“更好”取决于上下文:您的团队,您的项目和您的偏好。

泛化 (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', '')

...See something?

...看到什么?

This time—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')

...I don't know about you, but the first time I saw this I flipped my sh*t.

...我对你一无所知,但第一次见到这个,我就倒闭了。

Blew my mind.

设计考虑 (A Design Consideration)

Incidentally, this brings up a good point. 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 way better if we could just use partial (or partialRight), instead of both in sequence.

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

For us, this is a pretty easy fix. If we redefine buildUrl:

对于我们来说,这是一个非常简单的修复。 如果我们重新定义buildUrl

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

This new version passes the values we're likely to know up-front 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 get away with using 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 & 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:

注意:

  1. The arguments we're most likely to want to fix appear on the left. The one we want to vary is all the way on the right.

    我们最想修正的参数出现在左侧。 我们想要改变的就是一路在右边。
  2. 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 slick trick 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不同。

Here's the "need to know":

这是“需要知道的”:

  1. curry doesn't fix arguments immediately. The returned function takes as many arguments as the original function.

    curry不会立即解决参数。 返回的函数接受的参数与原始函数一样多。
  2. If you pass all the necessary arguments to the curried function, it will simple behave like buildUri.

    如果将所有必要的参数传递给buildUri函数,它将简单地表现为buildUri
  3. If you pass fewer arguments than the original function took, the curried function will automagically return the same thing you'd get by calling partial.

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

So, currying gives us the best of both worlds: Automatic partial application, and the ability to use our original function "as normal".

因此,currying为我们提供了两全其美的优势:自动部分应用程序, 以及 “正常”使用原始功能的能力。

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

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

There's a lot more to say about currying, in general. In particular, I'd be remiss if I didn't define it.

通常,关于curring还有很多要说的。 特别是,如果我没有定义它,我将被忽略。

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 )一次调用的函数转换为一系列函数调用的过程 ,在该函数中 ,我们一次传递每个变量。

I hid something in the above snippet. 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')

...Or, we can call it like this:

...或者,我们可以这样称呼它:

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

...Yeah, looks weird, I know. 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 sequence of function calls, where we pass only one argument with each call.

重要的curriedBuldUrl是: curriedBuldUrl可以称为函数调用序列 ,其中每次调用仅传递一个参数。

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.

这是将许多“一次全部”传递的变量的函数转换为我们称为“ curring”的“单参数调用”序列的过程。

摘要 ( Summary )

Let's recap the major takeaways.

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

  1. Partial application allows us to fix a function's arguments. This lets us derive new functions, with specific behavior, from other, more general functions. 2.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.

    部分应用程序使我们可以修复函数的参数。 这使我们能够从其他更通用的功能中派生具有特定行为的功能。 2. Currying将“一次全部”接受多个参数的函数转换为一系列函数调用,每个函数一次只涉及一个参数。 具有精心设计的参数顺序的咖喱函数便于部分应用。
  2. Ramda provides partial, partialRight, and curry utilities. Similar popular libraries include Underscore and Lodash...But I am quite partial to Ramda.

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

If you've got questions or use functional programming techniques in your own code, drop a line, or hit me on Twitter @PelekeS.

如果您有疑问或在自己的代码中使用函数式编程技术,请下一行,或者在Twitter @PelekeS上打我。

翻译自: https://scotch.io/tutorials/javascript-functional-programming-explained-partial-application-and-currying

javascript函数式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值