JavaScript関数型プログラミングの説明:部分適用とカリー化
序章
Redux JavaScriptライブラリ、 Reason 構文拡張とツールチェーン、および Cycle JavaScriptフレームワークの採用により、JavaScriptを使用した関数型プログラミングの関連性が高まっています。 関数思考にルーツを持つ2つの重要なアイデアは、複数の引数の関数を一連の関数呼び出しに変換するカリー化と、関数の一部の値を修正する部分適用です。関数を完全に評価せずに引数。 この記事では、これらのアイデアの実際の例をいくつか紹介し、それらが現れるいくつかの場所を特定して、あなたを驚かせる可能性があります。
これを読んだ後、次のことができるようになります。
- 部分適用とカリー化を定義し、2つの違いを説明します。
- 部分適用を使用して、関数の引数を修正します。
- 部分適用を容易にするカレー機能。
- 部分適用を容易にする設計機能。
部分適用のない例
多くのパターンと同様に、部分適用はコンテキストで理解しやすくなります。
このbuildUri
関数について考えてみます。
function buildUri (scheme, domain, path) { return `${scheme}://${domain}/${path}` }
私たちはこのように呼びます:
buildUri('https', 'twitter.com', 'favicon.ico')
これにより、文字列https://twitter.com/favicon.ico
が生成されます。
これは、大量のURLを作成する場合に便利な機能です。 ただし、主にWebで作業している場合は、http
またはhttps
以外のscheme
を使用することはめったにありません。
const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico') const googleHome = buildUri('https', 'google.com', '')
これらの2つの行の共通点に注意してください。どちらも最初の引数としてhttps
を渡します。 繰り返しを切り取って、次のようなものを書きたいと思います。
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
これを行うにはいくつかの方法があります。 部分適用でそれを達成する方法を見てみましょう。
部分適用:引数の修正
代わりに、次のことに同意しました。
const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')
私たちは次のように書きたいと思います。
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
概念的には、buildHttpsUri
は正確にbuildUri
と同じことを行いますが、scheme
引数の値は固定されています。
buildHttpsUri
は、次のように直接実装できます。
function buildHttpsUri (domain, path) { return `https://${domain}/${path}` }
これは私たちが望むことを実行しますが、まだ私たちの問題を完全には解決していません。 buildUri
を複製していますが、scheme
引数としてhttps
をハードコーディングしています。
部分適用ではこれを行うことができますが、buildUri
にすでにあるコードを利用することによって。 最初に、Ramdaと呼ばれる機能ユーティリティライブラリを使用してこれを行う方法を説明します。 次に、手作業でやってみます。
ラムダを使用する
Ramdaを使用すると、部分適用は次のようになります。
// Assuming we're in a node environment const R = require('ramda') // R.partial returns a new function (!) const buildHttpsUri = R.partial(buildUri, ['https'])
その後、次のことができます。
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
ここで何が起こったのかを分析してみましょう。
- Ramdaの
partial
関数を呼び出し、2つの引数を渡しました。1つはbuildUri
という関数で、もう1つは"https"
値を含むarrayです。 - 次に、Ramdaは、
buildUri
のように動作するが、最初の引数として"https"
を使用する新しい関数を返します。
配列にさらに値を渡すと、さらに引数が修正されます。
// 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')
これにより、特別な場合に合わせて構成することで、他の場所で作成した一般的なコードを再利用できます。
手動部分適用
実際には、部分適用を使用する必要がある場合は常に、partial
などのユーティリティを使用します。 しかし、説明のために、これを自分たちでやってみましょう。
最初にスニペットを見てから、分析してみましょう。
// 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')
何が起こったのかを分析してみましょう。
- 0行目で、
fixUriScheme
という関数を定義します。 この関数はscheme
を受け入れ、別の関数を返します。 - 1行目では、
fixUriScheme('https')
を呼び出した結果をbuildHttpsUri
という変数に保存します。この変数は、Ramdaで作成したバージョンとまったく同じように動作します。
関数fixUriScheme
は値を受け取り、関数を返します。 これにより、高階関数、つまりHOFになることを思い出してください。 この返された関数は、domain
とpath
の2つの引数のみを受け入れます。
この返された関数を呼び出すときは、domain
とpath
のみを明示的に渡しますが、1行目で渡したscheme
を記憶していることに注意してください。 これは、内部関数buildUriWithProvidedScheme
が、親関数が戻った後でも、その親関数のスコープ内のすべての値にアクセスできるためです。 これをクロージャーと呼びます。
これは一般化されます。 関数が別の関数を返すときはいつでも、返された関数は、親関数のスコープ内で初期化された変数にアクセスできます。 これは、クロージャを使用して状態をカプセル化する良い例です。
メソッドを持つオブジェクトを使用して、同様のことを行うことができます。
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')
この例では、UriBuilder
クラスの各インスタンスを特定のscheme
で構成します。 次に、buildUri
メソッドを呼び出すことができます。このメソッドは、ユーザーの目的のdomain
およびpath
を事前構成されたscheme
と組み合わせて、目的のURLを生成します。
一般化
最初に使用した例を思い出してください。
const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico') const googleHome = buildUri('https', 'google.com', '')
少し変更してみましょう:
const twitterHome = buildUri('https', 'twitter.com', '') const googleHome = buildUri('https', 'google.com', '')
今回は2つの共通点があります。どちらの場合もスキーム"https"
と、ここでは空の文字列であるパスです。
前に見たpartial
関数は、左から部分的に適用されます。 RamdaはpartialRightも提供しており、右から左に部分的に適用できます。
const buildHomeUrl = R.partialRight(buildUri, ['']) const twitterHome = buildHomeUrl('https', 'twitter.com') const googleHome = buildHomeUrl('https', 'google.com')
これをさらに進めることができます:
const buildHttpsHomeUrl = R.partial(buildHomeUrl, ['https']) const twitterHome = buildHttpsHomeUrl('twitter.com') const googleHome = buildHttpsHomeUrl('google.com')
設計上の考慮事項
scheme
とpath
の両方の引数をbuildUrl
に修正するには、最初にpartialRight
を使用し、次に結果にpartial
を使用する必要がありました。 。
これは理想的ではありません。 両方を順番に使用する代わりに、partial
(またはpartialRight
)を使用できるとよいでしょう。
これを修正できるかどうか見てみましょう。 buildUrl
を再定義すると、次のようになります。
function buildUrl (scheme, path, domain) { return `${scheme}://${domain}/${path}` }
この新しいバージョンは、最初に知っている可能性が高い値を渡します。 最後の引数domain
は、私たちが変更したいと思う可能性が最も高い引数です。 引数をこの順序で並べることは、経験則として適切です。
partial
のみを使用することもできます。
const buildHttpsHomeUrl = R.partial(buildUrl, ['https', ''])
これは、議論の順序が重要であるという点を思い起こさせます。 一部の注文は、他の注文よりも部分適用に便利です。 関数を部分適用で使用する場合は、時間をかけて引数の順序を検討してください。
カリー化と便利な部分適用
buildUrl
を別の引数の順序で再定義しました:
function buildUrl (scheme, path, domain) { return `${scheme}://${domain}/${path}` }
ご了承ください:
- 修正したいと思う可能性が最も高い引数は、左側に表示されます。 私たちが変えたいのは、ずっと右側です。
buildUri
は3つの引数の関数です。 つまり、実行するには3つのパスを渡す必要があります。
これを利用するために使用できる戦略があります。
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')
curry 関数は、 curries 関数を受け取り、partial
とは異なり、新しい関数を返します。
カリー化は、buildUrl
などの複数の変数を使用して一度に呼び出す関数を一連の関数呼び出しに変換するプロセスであり、各変数を一度に1つずつ渡します。
curry
は引数をすぐには修正しません。 返される関数は、元の関数と同じ数の引数を取ります。- カレー関数に必要なすべての引数を渡すと、
buildUri
のように動作します。 - 元の関数が取ったよりも少ない引数を渡すと、カレー関数は
partial
を呼び出した場合と同じものを自動的に返します。
カリー化は、自動部分適用と元の機能を使用する機能の両方の長所を提供します。
カリー化すると、関数の部分的に適用されたバージョンを簡単に作成できることに注意してください。 これは、引数の順序に注意している限り、カレー関数を部分的に適用すると便利だからです。
curriedbuildUrl
は、buildUri
と同じように呼び出すことができます。
const curriedBuildUrl = R.curry(buildUrl) // Outputs: `https://twitter.com/favicon.ico` curriedBuildUrl('https', 'favicon.ico', 'twitter.com')
このように呼ぶこともできます:
curriedBuildUrl('https')('favicon.ico')('twitter.com')
curriedBuildUrl('https')
は、buildUrl
のように動作する関数を返しますが、そのスキームは"https"
に固定されていることに注意してください。
次に、この関数を"favicon.ico"
ですぐに呼び出します。 これにより、buildUrl
のように動作する別の関数が返されますが、そのスキームは"https"
に固定され、パスは空の文字列に固定されています。
最後に、"twitter.com"
を使用してこの関数を呼び出します。 これが最後の引数であるため、関数はhttp://twitter.com/favicon.ico
の最終値に解決されます。
重要なポイントは次のとおりです。curriedBuldUrl
は一連の関数呼び出しとして呼び出すことができ、呼び出しごとに1つの引数のみを渡します。 これは、「一度に」渡された多くの変数の関数を、カリー化と呼ばれる一連の「1つの引数の呼び出し」に変換するプロセスです。
結論
主なポイントを要約してみましょう。
- 部分適用を使用すると、関数の引数を修正できます。 これにより、他のより一般的な関数から、特定の動作を備えた新しい関数を導出できます。
- Currying は、複数の引数を「一度に」受け入れる関数を一連の関数呼び出しに変換します。各関数呼び出しには、一度に1つの引数しか含まれません。 適切に設計された引数の順序を持つカレー関数は、部分的に適用するのに便利です。
- Ramda は、
partial
、partialRight
、およびcurry
ユーティリティを提供します。 同様の人気のあるライブラリには、UnderscoreおよびLodashが含まれます。