Ramda.js を通してカリー化に触れてみる
こんにちは、フロントエンドエンジニアの辻です。
前回の記事では JavaScript の関数型ライブラリ「Ramda.js」を紹介しました。
今回は Ramda.js を通してカリー化に触れてみます!
カリー化とは?
前回の記事でサラリと触れましたが、改めてカリー化について書きます。
Ramda.js 固有の考え方ではなく、関数型プログラミングに登場する考え方です。
カリー化を Wikipedia で調べてみると、以下のような説明が見つかります。
カリー化 (currying, カリー化された=curried) とは、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。
カリー化 Wikipedia より引用
…なかなかに難しいですね。
JavaScript を例にとって、見ていこうと思います。
例えば、以下のような引数を4つとる関数 calc
があります。calc
関数の実行内容は単純で、以下の計算をしているだけです。
- 第4引数に第1引数を加算する
- 1.の計算結果を第2引数で乗算する
- 2.の計算結果を第3引数で除算する
- 3.の結果を返す
const calc = (num1, num2, num3, target) => {
return (((target + num1) * num2) / num3)
}
calc(10, 20, 30, 40) // 33.333…
では、calc
関数をカリー化を意識して書き直してみます。
const calc = (num1) => {
return (num2) => {
return (num3) => {
return (target) => {
return (((target + num1) * num2) / num3)
}
}
}
}
calc(10)(20)(30)(40) // 33.333…
カリー化する前後で、calc
関数の実行方法が変わりました。
カリー化前は4つの引数全てを入れて実行していましたが、カリー化後は引数を1つずつ入れて関数を実行しています。
カリー化のメリット
さて、実行方法が変わったのは分かりましたが、実際のところ、この変化の何が嬉しいのでしょうか?
先程のカリー化後の calc
関数を例にとってみます。
まず calc
関数に、加算する数 10、乗算する数 20、除算する数 30 の3つの引数を設定します。その返り値を calc2
としましょう。calc2
は「引数に対して、10を加算して、20で乗算して、30で除算するただの関数」ですので、任意の数値を引数にとって実行できます。
calc2(40)
とすれば (((40 + 10) * 20) / 30) = 33.333…
。calc2(50)
とすれば (((50 + 10) * 20) / 30) = 40
。
…と計算できます。
const calc = (num1) => {
return (num2) => {
return (num3) => {
return (target) => {
return (((target + num1) * num2) / num3)
}
}
}
}
const calc2 = calc(10)(20)(30) // 加算する数 10, 乗算する数 20, 除算する数 30,
calc2(40) // 33.333…
calc2(50) // 40
calc2(60) // 46.666…
calc2(70) // 53.333…
このようにカリー化された関数には「引数を使って関数の実行内容を柔軟に変更できる」・「関数の実行を保留し、任意のタイミングで実行できる」という特徴があります。
Ramda.js でカリー化に触れてみる
ここからが本題です。
Ramda.js を通してカリー化に触れてみます。
Ramda.js 公式サイトの whats-different に記載の通り、実は Ramda.js の関数は自動的にカリー化されるよう設計されています。
ですので、Ramda.js を導入した時点から特に何の設定もなしにカリー化の恩恵を受けられます。
前回の記事で登場した omit()
を例にとってみます。
以下のように、R.omit()
に引数を 1 つだけ指定すると「引数のオブジェクトからプロパティ a を取り除く関数」(omitA
関数)が返却されます。omitA
関数はただの関数ですので、他のオブジェクトを引数にとり、任意のタイミングで実行できます。
import * as R from "ramda"
const omitA = R.omit(["a"]) // オブジェクトからプロパティ a を取り除く関数
omitA({ a: "a", b: "b", c: "c" }) // { b: "b", c: "c" }
omitA({ a: "a", d: "d", e: "e" }) // { d: "d", e: "e" }
omitA({}) // {}
もう一つの例として equals()
も使ってみます。
以下のように、R.equals()
に引数を一つだけ指定すると、「引数が { a: "a", b: "b", c: "c" }
と同一かを判定する関数」(equalsObj
関数)が返却されます。omitA
関数と同じく、equalsObj
関数も任意のタイミングで実行できます。
import * as R from "ramda"
const equalsObj = R.equals({ a: "a", b: "b", c: "c" }) // { a: "a", b: "b", c: "c" } と同一かを判定する関数
equalsObj({ a: "a", b: "b", c: "c" }) // true
equalsObj({ a: "a", b: "b", d: "d" }) // false
このように、Ramda.js の関数はカリー化のおかげで「はじめに引数を設定して関数の実行内容を決定する → 任意のタイミングで関数を実行する」事が可能です。
おわりに
今回は Ramda.js を通して、カリー化に触れてみました。
カリー化された関数を使えば、引数の設定と関数の実行を切り離せて、柔軟なコードを書けます。
また機会があれば、Ramda.js の機能や特徴に触れていきたいと思います。
Gaji-Labo は新規事業やサービス開発に取り組む、事業会社・スタートアップへの支援を行っています。
弊社では、Next.js を用いた Web アプリケーションのフロントエンド開発をリードするフロントエンドエンジニアを募集しています!さまざまなプロダクトやチームに関わりながら、一緒に成長を体験しませんか?
もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください!