検索

JavaScript関数のカリー化

Dec 06, 2017 pm 04:11 PM
javascriptjs関数

カレーとは何ですか?

コンピュータサイエンスにおいて、カリー化(英語:Currying)は、カリー化またはカリー化とも訳され、複数のパラメータを受け入れる関数を、単一のパラメータ(元の関数の最初のパラメータ)を受け入れる関数に変換することです。残りのパラメータを受け入れ、結果を返す新しい関数を返します。この手法は、モーゼス シェーンフィンケルとゴットロブ フレーゲによって発明されましたが、論理学者ハスケル ゲイリーにちなんでクリストファー ストラチーによって命名されました。

直観的に、カリー化は、特定のパラメーターを修正すると、残りのパラメーターを受け入れる関数が得られると主張します。
理論コンピューターサイエンスでは、カリー化は、単一のパラメーターのみを受け入れるラムダ計算など、単純な理論モデルで複数のパラメーターを使用して関数を研究する方法を提供します。
関数カリー化の二重性は Uncurrying です。これは、匿名の単一パラメーター関数を使用して複数パラメーター関数を実装する方法です。

わかりやすい

カリー化の概念は実際には非常に単純で、パラメーターの一部を関数に渡して呼び出し、残りのパラメーターを処理する関数を返すだけです。

3 つの数値の合計を求める関数を実装する必要がある場合:

<span style="font-size: 16px;">function add(x, y, z) {<br>  return x + y + z;<br>}<br>console.log(add(1, 2, 3)); // 6<br></span>
<span style="font-size: 16px;">var add = function(x) {<br>  return function(y) {<br>    return function(z) {<br>      return x + y + z;<br>    }<br>  }<br>}<br><br>var addOne = add(1);<br>var addOneAndTwo = addOne(2);<br>var addOneAndTwoAndThree = addOneAndTwo(3);<br><br>console.log(addOneAndTwoAndThree);<br></span>

ここでは、パラメーターを受け入れて新しい関数を返す add 関数を定義します。 add を呼び出した後、返された関数はクロージャを通じて add の最初のパラメータを記憶します。すべてを一度に呼び出すのは少し面倒ですが、幸いなことに、特別なカリー ヘルパー関数を使用すると、そのような関数の定義と呼び出しを簡単に行うことができます。

ES6 のアロー関数を使用すると、上記の追加を次のように実装できます:

<span style="font-size: 16px;">const add = x => y => z => x + y + z;<br></span>

アロー関数を使用した方がより明確であるようです。

部分的な機能?

この関数を見てみましょう:

<span style="font-size: 16px;">function ajax(url, data, callback) {<br>  // ..<br>}<br></span>

複数の異なるインターフェイスに対して HTTP リクエストを開始する必要があるシナリオがあります。これを行うには 2 つの方法があります:

  • ajax を呼び出す。 () 関数の場合、グローバル URL 定数を渡します。

  • 事前に設定された URL パラメーターを使用して関数参照を作成します。

次に、内部で ajax() リクエストを開始する新しい関数を作成します。さらに、他の 2 つの実際のパラメーターの受信を待機している間に、ajax() の最初の実パラメーターを手動で設定します。 APIアドレスに注意してください。

最初のアプローチでは、次の呼び出しメソッドを生成します:

<span style="font-size: 16px;">function ajaxTest1(data, callback) {<br>  ajax('http://www.test.com/test1', data, callback);<br>}<br><br>function ajaxTest2(data, callback) {<br>  ajax('http://www.test.com/test2', data, callback);<br>}<br></span>

これら 2 つの類似した関数については、次のパターンを抽出することもできます:

<span style="font-size: 16px;">function beginTest(callback) {<br>  ajaxTest1({<br>    data: GLOBAL_TEST_1,<br>  }, callback);<br>}<br></span>

このパターンを見たことがあると思います: We関数呼び出しサイトで実パラメータを仮パラメータに適用します。ご覧のとおり、最初は引数の一部のみを適用し (具体的には URL パラメーターに適用します)、残りは後で適用します。

上記の概念は部分関数の定義です。部分関数とは、関数のパラメーターの数を減らすプロセスです。ここでのパラメーターの数は、渡されることが期待される仮パラメーターの数を指します。 ajaxTest1() により、元の関数 ajax() のパラメータの数を 3 から 2 に減らしました。

partial() 関数を次のように定義します。

<span style="font-size: 16px;">function partial(fn, ...presetArgs) {<br>  return function partiallyApplied(...laterArgs) {<br>    return fn(...presetArgs, ...laterArgs);<br>  }<br>}<br></span>

partial() 関数は、実際のパラメータを部分的に適用する関数を表す fn パラメータを受け取ります。次に、fn 仮パラメータの後に、presetArgs 配列が後で渡される実際のパラメータを収集し、後で使用できるように保存します。

新しい内部関数を作成して返します (わかりやすくするために、partialApplied(..) という名前を付けます)。この内部関数では、lateArgs 配列がすべての実際のパラメーターを収集します。

アロー関数を使用すると、より簡潔になります:

<span style="font-size: 16px;">var partial =<br>  (fn, ...presetArgs) =><br>    (...laterArgs) =><br>      fn(...presetArgs, ...laterArgs);<br></span>

このモードの部分関数を使用して、前のコードを再構築します:

<span style="font-size: 16px;">function ajax(url, data, callback) {<br>  // ..<br>}<br><br>var ajaxTest1 = partial(ajax, 'http://www.test.com/test1');<br>var ajaxTest2 = partial(ajax, 'http://www.test.com/test1');<br></span>

beginTest() 関数についてもう一度考えてみましょう。partial() を使用します。どのようにリファクタリングすればよいでしょうか?

<span style="font-size: 16px;">function ajax(url, data, callback) {<br>  // ..<br>}<br><br>// 版本1<br>var beginTest = partial(ajax, 'http://www.test.com/test1', {<br>  data: GLOBAL_TEST_1,<br>});<br><br>// 版本2<br>var ajaxTest1 = partial(ajax, 'http://www.test.com/test1');<br>var beginTest = partial(ajaxTest1, {<br>  data: GLOBAL_TEST_1,<br>});<br></span>

一度に 1 つずつ渡します

上の例で、バージョン 1 に比べてバージョン 2 の利点が理解できたと思います。はい、カリー化とは、複数のパラメーターを持つ関数の変換は 1 つの関数を実行するプロセスです。一度に。関数が呼び出されるたびに、引数を 1 つだけ受け取り、すべての引数が渡されるまで関数を返します。

The process of converting a function that takes multiple arguments into a function that takes them one at a time.

Each time the function is called it only accepts one argument and returns a function that takes one argument until all arguments are passed.

假设我们已经创建了一个柯里化版本的ajax()函数curriedAjax():

<span style="font-size: 16px;">curriedAjax('http://www.test.com/test1')<br>  ({<br>    data: GLOBAL_TEST_1,<br>  })<br>  (function callback(data) {<br>    // dosomething<br>  });<br></span>

我们将三次调用分别拆解开来,这也许有助于我们理解整个过程:

<span style="font-size: 16px;">var ajaxTest1 = curriedAjax('http://www.test.com/test1');<br><br>var beginTest = ajaxTest1({<br>  data: GLOBAL_TEST_1,<br>});<br><br>var ajaxCallback = beginTest(function callback(data) {<br>  // dosomething<br>});<br></span>

实现柯里化

那么,我们如何来实现一个自动的柯里化的函数呢?

<span style="font-size: 16px;">var currying = function(fn) {<br>  var args = [];<br><br>  return function() {<br>    if (arguments.length === 0) {<br>      return fn.apply(this, args); // 没传参数时,调用这个函数<br>    } else {<br>      [].push.apply(args, arguments); // 传入了参数,把参数保存下来<br>      return arguments.callee; // 返回这个函数的引用<br>    }<br>  }<br>}<br></span>

调用上述currying()函数:

<span style="font-size: 16px;">var cost = (function() {<br>  var money = 0;<br>  return function() {<br>    for (var i = 0; i       money += arguments[i];<br>    }<br>    return money;<br>  }<br>})();<br><br>var cost = currying(cost);<br><br>cost(100); // 传入了参数,不真正求值<br>cost(200); // 传入了参数,不真正求值<br>cost(300); // 传入了参数,不真正求值<br><br>console.log(cost()); // 求值并且输出600<br></span>

上述函数是我之前的JavaScript设计模式与开发实践读书笔记之闭包与高阶函数所写的currying版本,现在仔细思考后发现仍旧有一些问题。

我们在使用柯里化时,要注意同时为函数预传的参数的情况。

因此把上述柯里化函数更改如下:

<span style="font-size: 16px;">var currying = function(fn) {<br>  var args = Array.prototype.slice.call(arguments, 1);<br><br>  return function() {<br>    if (arguments.length === 0) {<br>      return fn.apply(this, args); // 没传参数时,调用这个函数<br>    } else {<br>      [].push.apply(args, arguments); // 传入了参数,把参数保存下来<br>      return arguments.callee; // 返回这个函数的引用<br>    }<br>  }<br>}<br></span>

使用实例:

<span style="font-size: 16px;">var cost = (function() {<br>  var money = 0;<br>  return function() {<br>    for (var i = 0; i       money += arguments[i];<br>    }<br>    return money;<br>  }<br>})();<br><br>var cost = currying(cost, 100);<br>cost(200); // 传入了参数,不真正求值<br>cost(300); // 传入了参数,不真正求值<br><br>console.log(cost()); // 求值并且输出600<br></span>

你可能会觉得每次都要在最后调用一下不带参数的cost()函数比较麻烦,并且在cost()函数都要使用arguments参数不符合你的预期。我们知道函数都有一个length属性,表明函数期望接受的参数个数。因此我们可以充分利用预传参数的这个特点。

借鉴自mqyqingfeng:

<span style="font-size: 16px;">function sub_curry(fn) {<br>  var args = [].slice.call(arguments, 1);<br>  return function() {<br>    return fn.apply(this, args.concat([].slice.call(arguments)));<br>  };<br>}<br><br>function curry(fn, length) {<br><br>  length = length || fn.length;<br><br>  var slice = Array.prototype.slice;<br><br>  return function() {<br>    if (arguments.length       var combined = [fn].concat(slice.call(arguments));<br>      return curry(sub_curry.apply(this, combined), length - arguments.length);<br>    } else {<br>      return fn.apply(this, arguments);<br>    }<br>  };<br>}<br></span>

在上述函数中,我们在currying的返回函数中,每次把arguments.length和fn.length作比较,一旦arguments.length达到了fn.length的数量,我们就去调用fn(return fn.apply(this, arguments);)

验证:

<span style="font-size: 16px;">var fn = curry(function(a, b, c) {<br>  return [a, b, c];<br>});<br><br>fn("a", "b", "c") // ["a", "b", "c"]<br>fn("a", "b")("c") // ["a", "b", "c"]<br>fn("a")("b")("c") // ["a", "b", "c"]<br>fn("a")("b", "c") // ["a", "b", "c"]<br></span>

bind方法的实现

使用柯里化,能够很方便地借用call()或者apply()实现bind()方法的polyfill。

<span style="font-size: 16px;">Function.prototype.bind = Function.prototype.bind || function(context) {<br>  var me = this;<br>  var args = Array.prototype.slice.call(arguments, 1);<br>  return function() {<br>    var innerArgs = Array.prototype.slice.call(arguments);<br>    var finalArgs = args.concat(innerArgs);<br>    return me.apply(contenxt, finalArgs);<br>  }<br>}<br></span>

上述函数有的问题在于不能兼容构造函数。我们通过判断this指向的对象的原型属性,来判断这个函数是否通过new作为构造函数调用,来使得上述bind方法兼容构造函数。

Function.prototype.bind() by MDN如下说到:

绑定函数适用于用new操作符 new 去构造一个由目标函数创建的新的实例。当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。然而, 原先提供的那些参数仍然会被前置到构造函数调用的前面。

这是基于MVC的JavaScript Web富应用开发的bind()方法实现:

<span style="font-size: 16px;">Function.prototype.bind = function(oThis) {<br>  if (typeof this !== "function") {<br>    throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");<br>  }<br><br>  var aArgs = Array.prototype.slice.call(arguments, 1),<br>    fToBind = this,<br>    fNOP = function() {},<br>    fBound = function() {<br>      return fToBind.apply(<br>        this instanceof fNOP && oThis ? this : oThis || window,<br>        aArgs.concat(Array.prototype.slice.call(arguments))<br>      );<br>    };<br><br>  fNOP.prototype = this.prototype;<br>  fBound.prototype = new fNOP();<br><br>  return fBound;<br>};<br></span>

反柯里化(uncurrying)

可能遇到这种情况:拿到一个柯里化后的函数,却想要它柯里化之前的版本,这本质上就是想将类似f(1)(2)(3)的函数变回类似g(1,2,3)的函数。

下面是简单的uncurrying的实现方式:

<span style="font-size: 16px;">function uncurrying(fn) {<br>  return function(...args) {<br>    var ret = fn;<br><br>    for (let i = 0; i       ret = ret(args[i]); // 反复调用currying版本的函数<br>    }<br><br>    return ret; // 返回结果<br>  };<br>}<br></span>

注意,不要以为uncurrying后的函数和currying之前的函数一模一样,它们只是行为类似!

<span style="font-size: 16px;">var currying = function(fn) {<br>  var args = Array.prototype.slice.call(arguments, 1);<br><br>  return function() {<br>    if (arguments.length === 0) {<br>      return fn.apply(this, args); // 没传参数时,调用这个函数<br>    } else {<br>      [].push.apply(args, arguments); // 传入了参数,把参数保存下来<br>      return arguments.callee; // 返回这个函数的引用<br>    }<br>  }<br>}<br><br>function uncurrying(fn) {<br>  return function(...args) {<br>    var ret = fn;<br><br>    for (let i = 0; i       ret = ret(args[i]); // 反复调用currying版本的函数<br>    }<br><br>    return ret; // 返回结果<br>  };<br>}<br><br>var cost = (function() {<br>  var money = 0;<br>  return function() {<br>    for (var i = 0; i       money += arguments[i];<br>    }<br>    return money;<br>  }<br>})();<br><br>var curryingCost = currying(cost);<br>var uncurryingCost = uncurrying(curryingCost);<br>console.log(uncurryingCost(100, 200, 300)()); // 600<br></span>

柯里化或偏函数有什么用?

无论是柯里化还是偏应用,我们都能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。

另一个最能体现柯里化应用的的是,当函数只有一个形参时,我们能够比较容易地组合它们(单一职责原则(Single responsibility principle))。因此,如果一个函数最终需要三个实参,那么它被柯里化以后会变成需要三次调用,每次调用需要一个实参的函数。当我们组合函数时,这种单元函数的形式会让我们处理起来更简单。

归纳下来,主要为以下常见的三个用途:

  • 延迟计算

  • 参数复用

  • 関数の動的生成

以上の内容は、JavaScript関数のカリー化についての説明ですので、皆様のお役に立てれば幸いです。

関連する推奨事項:

JSカリー化の詳細な例

JSのアンチカリー化

JavaScript関数カリー化の詳細な説明

以上がJavaScript関数のカリー化の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
ブラウザを超えて:現実世界のJavaScriptブラウザを超えて:現実世界のJavaScriptApr 12, 2025 am 12:06 AM

現実世界におけるJavaScriptのアプリケーションには、サーバー側のプログラミング、モバイルアプリケーション開発、モノのインターネット制御が含まれます。 2。モバイルアプリケーションの開発は、ReactNativeを通じて実行され、クロスプラットフォームの展開をサポートします。 3.ハードウェアの相互作用に適したJohnny-Fiveライブラリを介したIoTデバイス制御に使用されます。

next.jsを使用してマルチテナントSaaSアプリケーションを構築する(バックエンド統合)next.jsを使用してマルチテナントSaaSアプリケーションを構築する(バックエンド統合)Apr 11, 2025 am 08:23 AM

私はあなたの日常的な技術ツールを使用して機能的なマルチテナントSaaSアプリケーション(EDTECHアプリ)を作成しましたが、あなたは同じことをすることができます。 まず、マルチテナントSaaSアプリケーションとは何ですか? マルチテナントSaaSアプリケーションを使用すると、Singの複数の顧客にサービスを提供できます

next.jsを使用してマルチテナントSaaSアプリケーションを構築する方法(フロントエンド統合)next.jsを使用してマルチテナントSaaSアプリケーションを構築する方法(フロントエンド統合)Apr 11, 2025 am 08:22 AM

この記事では、許可によって保護されたバックエンドとのフロントエンド統合を示し、next.jsを使用して機能的なedtech SaaSアプリケーションを構築します。 FrontEndはユーザーのアクセス許可を取得してUIの可視性を制御し、APIリクエストがロールベースに付着することを保証します

JavaScript:Web言語の汎用性の調査JavaScript:Web言語の汎用性の調査Apr 11, 2025 am 12:01 AM

JavaScriptは、現代のWeb開発のコア言語であり、その多様性と柔軟性に広く使用されています。 1)フロントエンド開発:DOM操作と最新のフレームワーク(React、Vue.JS、Angularなど)を通じて、動的なWebページとシングルページアプリケーションを構築します。 2)サーバー側の開発:node.jsは、非ブロッキングI/Oモデルを使用して、高い並行性とリアルタイムアプリケーションを処理します。 3)モバイルおよびデスクトップアプリケーション開発:クロスプラットフォーム開発は、反応および電子を通じて実現され、開発効率を向上させます。

JavaScriptの進化:現在の傾向と将来の見通しJavaScriptの進化:現在の傾向と将来の見通しApr 10, 2025 am 09:33 AM

JavaScriptの最新トレンドには、TypeScriptの台頭、最新のフレームワークとライブラリの人気、WebAssemblyの適用が含まれます。将来の見通しは、より強力なタイプシステム、サーバー側のJavaScriptの開発、人工知能と機械学習の拡大、およびIoTおよびEDGEコンピューティングの可能性をカバーしています。

javascriptの分解:それが何をするのか、なぜそれが重要なのかjavascriptの分解:それが何をするのか、なぜそれが重要なのかApr 09, 2025 am 12:07 AM

JavaScriptは現代のWeb開発の基礎であり、その主な機能には、イベント駆動型のプログラミング、動的コンテンツ生成、非同期プログラミングが含まれます。 1)イベント駆動型プログラミングにより、Webページはユーザー操作に応じて動的に変更できます。 2)動的コンテンツ生成により、条件に応じてページコンテンツを調整できます。 3)非同期プログラミングにより、ユーザーインターフェイスがブロックされないようにします。 JavaScriptは、Webインタラクション、シングルページアプリケーション、サーバー側の開発で広く使用されており、ユーザーエクスペリエンスとクロスプラットフォーム開発の柔軟性を大幅に改善しています。

pythonまたはjavascriptの方がいいですか?pythonまたはjavascriptの方がいいですか?Apr 06, 2025 am 12:14 AM

Pythonはデータサイエンスや機械学習により適していますが、JavaScriptはフロントエンドとフルスタックの開発により適しています。 1. Pythonは、簡潔な構文とリッチライブラリエコシステムで知られており、データ分析とWeb開発に適しています。 2。JavaScriptは、フロントエンド開発の中核です。 node.jsはサーバー側のプログラミングをサポートしており、フルスタック開発に適しています。

JavaScriptをインストールするにはどうすればよいですか?JavaScriptをインストールするにはどうすればよいですか?Apr 05, 2025 am 12:16 AM

JavaScriptは、最新のブラウザにすでに組み込まれているため、インストールを必要としません。開始するには、テキストエディターとブラウザのみが必要です。 1)ブラウザ環境では、タグを介してHTMLファイルを埋め込んで実行します。 2)node.js環境では、node.jsをダウンロードしてインストールした後、コマンドラインを介してJavaScriptファイルを実行します。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター

SecLists

SecLists

SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

DVWA

DVWA

Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、

SublimeText3 Linux 新バージョン

SublimeText3 Linux 新バージョン

SublimeText3 Linux 最新バージョン

EditPlus 中国語クラック版

EditPlus 中国語クラック版

サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません