Ramda.js を使って合成関数を作成してみる
こんにちは、フロントエンドエンジニアの辻です。
前回の記事では「Ramda.js」を通してカリー化に触れてみました。
今回は Ramda.js の pipe() を使って、関数を合成していきます!
Ramda.js の pipe()
以前の記事にて pipe() を紹介しましたが、改めて説明します。
Ramda.js の pipe() は、引数の関数を合成して 1 つの関数(合成関数)にまとめる関数です。pipe() は実行時に関数を引数にとります。その引数のうち、第一引数の関数を起点にして、順次 関数を実行する関数を生成します。
const piped = R.pipe(
(i) => i + 20, // はじめに実行される
(i) => i * 3, // 2番目に実行される
(i) => i * i // 3番目に実行される
)
piped(10) // 8100 = ((10 + 20) * 3) ** 2pipe() は連続した処理に効果的
pipe() によって生成される関数が威力を発揮する場面は多々あります。
特に「特定のデータに連続した処理を実行する」場面では、非常に心強いです。
例えば、TypeScript コードにおいて以下のような Student 型があるとします。
type Student = {
name: string
age: number
score: {
math: number
english: number
chemistry: number
physics: number
biology: number
}
}Student 型のデータに次の 5 ステップを実行するプログラムを書くとしましょう。
scoreのmathに 10 加算scoreのenglishから 10 減算ageが 20 以下ならscoreのphysicsに 5 加算scoreのmathが 50 以下ならchemistryに 20 加算ageが 20 以下 かつscoreのchemistryが 70 以上ならbiologyに 10 加算
まずは手続き的な関数を書いてみます。
const handleStudent = (student: Student) => {
// 1.
student.score.math += 10
// 2.
student.score.english -= 10
// 3.
if (student.age <= 20) student.score.physics += 5
// 4.
if (student.score.math <= 50) student.score.chemistry += 20
// 5.
if (student.age <= 20 && student.score.chemistry >= 70) student.score.biology += 10
return student
}さて、ステップ 1 〜 5 の処理を実行できる handleStudent 関数は作成できたものの、どうも使いづらさが残ります。
ここで「handleStudent 関数のステップ 1 〜 5 のうち、3 のみを変更した関数がほしい」との要望があったとします。
手っ取り早い方法は、handleStudent 関数をコピーして新しい関数を作成し、ステップ 3 の処理だけ調整する方法ですね。
もしくは handleStudent 関数に真偽値の引数を 1 つ設けて、ステップ 3 の実行を切り分けても良いでしょう。
…では、もしステップ数が 10・20 あるとしたら、どうでしょう?
一部のステップだけでなく、複数のステップを変更する必要があるとしたら?
ステップの順番を入れ替える必要があるとしたら?
単純に handleStudent 関数を改変し続けていけば、重厚長大なプログラムが出来てしまうでしょう。どこかで戦略を練る必要があります。
ここで pipe() の出番です!handleStudent 関数を Ramda.js の pipe() を使って改善してみます。
まずは ステップ 1 〜 5 を、1 つの関数に分解していきいます。
// 1.
const addMath = (student: Student) => {
student.score.math += 10
return student
}
// 2.
const subEnglidh = (student: Student) => {
student.score.english -= 10
return student
}
// 3.
const addPhysics = (student: Student) => {
if (student.age <= 20) student.score.physics += 5
return student
}
// 4.
const addChemistry = (student: Student) => {
if (student.score.math <= 50) student.score.chemistry += 20
return student
}
// 5.
const addBiology = (student: Student) => {
if (student.age <= 20 && student.score.chemistry >= 70) student.score.biology += 10
return student
}次に分解した 5 つの関数を pipe() の引数にいれて関数を合成します。
import * as R from "ramda"
// pipe で合成した関数
const pipedHandleStudent = R.pipe(
addMath,
subEnglidh,
addPhysics,
addChemistry,
addBiology
)
// Equal? true
console.log(
"Equal? ",
R.equals(
handleStudent(R.clone(studentA)),
pipedHandleStudent(R.clone(studentA))
)
)これではじめに作成した handleStudent 関数と同じ処理を実行できる pipedHandleStudent 関数を合成できました。
さて、ここで「ステップを追加した処理を新たに作成したい」との要望があったとします。
対応方法はシンプルです。
これまでと同様、小さな関数を必要な分だけ作成して、pipe() の引数に追加するだけで OK です。
pipe() により生成される関数は、引数の設定時に実行内容が決定づけられます。
したがって、pipedHandleStudent 関数を残したままに、新しい関数 pipedNewHandleStudent を作成できます。
import * as R from "ramda"
// pipe で合成した関数
const pipedHandleStudent = R.pipe(
addMath,
subEnglidh,
addPhysics,
addChemistry,
addBiology
)
// pipe で新しい関数をもう 1 つ作成
const pipedNewHandleStudent = R.pipe(
addMath,
subEnglidh,
addPhysics,
addChemistry,
addBiology,
newStepFunc1, // 追加
newStepFunc2, // 追加
newStepFunc3, // 追加
newStepFunc4, // 追加
newStepFunc5, // 追加
// …
)また、別の要望があったとしましょう。
要望の内容は「はじめに作成した pipedHandleStudent() 関数の実行順を逆転させた処理が新たに欲しい」とします。
どうすれば対応できるか…は明白ですね。pipe() の引数の順番だけ変えれば OK です。
(引数の順番をそのままで compose() を使っても、同じ関数が得られます。)
import * as R from "ramda"
// pipe で合成した関数
const pipedHandleStudent = R.pipe(
addMath,
subEnglidh,
addPhysics,
addChemistry,
addBiology
)
const pipedNewHandleStudent = R.pipe(
// … 略
)
// pipedHandleStudent() の実行順を逆転させた関数
const pipedReverseHandleStudent = R.pipe(
addBiology,
addChemistry,
addPhysics,
subEnglidh,
addMath
)このようにpipe() 関数を使うと、小さな関数を合成して、大きな処理をこなす関数を作成できます。
ステップの 1 つ 1 つを小さな関数で定義できるので、単体テストも行いやすく、保守性・可読性もあがります。
また、pipe() の引数の順序を組み替えるだけで、簡単に実行順序を変更できるので、改修しやすくもなります。
まとめ
今回は Ramda.js の pipe() を使って、関数を合成してみました!pipe() は Ramda.js を代表する強力な機能ですので、ぜひ機会があれば使ってみてください。
Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります
フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。
「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」
フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。
オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。
Next.js, React, TypeScript の相談をする!






