JavaScript関数型プログラミングの説明:部分適用とカリー化

提供:Dev Guides
移動先:案内検索

序章

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になることを思い出してください。 この返された関数は、domainpathの2つの引数のみを受け入れます。

この返された関数を呼び出すときは、domainpathのみを明示的に渡しますが、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')

設計上の考慮事項

schemepathの両方の引数を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 は、partialpartialRight、およびcurryユーティリティを提供します。 同様の人気のあるライブラリには、UnderscoreおよびLodashが含まれます。