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で作業している場合は、 scheme
以外 http
また https
:
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
、ただしハードコーディング https
そのように scheme
口論。
部分適用はこれを可能にしますが、私たちがすでに持っているコードを利用することによって 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')
ここで何が起こったのかを分析してみましょう。
- ラムダと呼んだ
partial
関数、および2つの引数を渡しました:最初に、呼び出された関数buildUri
、および2番目に、1つを含む配列"https"
価値。 - 次に、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になることを思い出してください。 この返された関数は、次の2つの引数のみを受け入れます。 domain
と path
.
この返された関数を呼び出すときは、明示的に渡すだけであることに注意してください domain
と path
、しかしそれは覚えています scheme
1行目を通過しました。 これは、内部関数が 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"
、どちらの場合も、パス、ここでは空の文字列。
The 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が含まれます。