読み込み中...

JavaScriptでパイプライン演算子を使いこなす12の手順

JavaScriptのパイプライン演算子で関数合成を実現する JS
この記事は約25分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●パイプライン演算子とは

みなさん、JavaScriptでコードを書いていると、関数の呼び出しが複雑になって読みづらくなることはありませんか?

そんな時に役立つのがパイプライン演算子です。パイプライン演算子を使えば、関数合成を簡潔に表現できるんです。

○関数合成とは

関数合成って何だろう?と思う方もいるかもしれません。

関数合成とは、複数の関数を組み合わせて新しい関数を作る手法のことです。

例えば、文字列を大文字に変換する関数と、文字列の長さを返す関数を合成すれば、文字列を大文字に変換しつつ長さを返す新しい関数ができあがります。

こんな風に関数を組み合わせることで、コードの再利用性が高まるんですね。

○パイプライン演算子の基本的な使い方

では、パイプライン演算子を使ってみましょう。

パイプライン演算子は、”|>”という記号で表されます。

関数fとgがあった時、”x |> f |> g”と書くと、まずxをfに渡し、その結果をgに渡すという流れになります。

これなら、関数の適用順序が一目瞭然ですね。

○サンプルコード1:パイプライン演算子の基本構文

具体的なコードを見てみましょう。

ここでは、数値を2倍にして、その結果を文字列に変換する例を見てみましょう。

const double = (n) => n * 2;
const toString = (n) => n.toString();

const result = 5 |> double |> toString;
console.log(result); // "10"

5を|>の左側に置き、その右側にdoubleとtoStringを|>でつないでいます。

これにより、5がdoubleに渡され、その結果がtoStringに渡されます。

最終的にresultには”10″が入ります。

パイプライン演算子を使えば、このようにデータの流れが明確になるので、コードが読みやすくなるんです。

●JavaScriptでパイプライン演算子を使う方法

さて、パイプライン演算子の基本は理解できましたね。

でも、実際にJavaScriptでパイプライン演算子を使うにはどうすればいいのでしょうか?

実は現時点では、パイプライン演算子はまだJavaScriptの正式な仕様に含まれていないんです。

でも心配ありません。Babelというツールを使えば、今すぐにでもパイプライン演算子を使えるようになりますよ。

○Babelプラグインのインストールと設定

Babelは、最新のJavaScript構文を古いブラウザでも動くようにトランスパイルしてくれる便利なツールです。

パイプライン演算子を使うには、Babelの設定ファイルに以下のような記述を追加します。

{
  "plugins": [
    ["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }]
  ]
}

これで、Babelがパイプライン演算子を認識してくれるようになります。

あとは、npm installでプラグインをインストールすれば準備完了です。

○サンプルコード2:Babelを使ったパイプライン演算子の実装

それでは、Babelを使ってパイプライン演算子を実装してみましょう。

先ほどの例を少し変えて、数値を2倍にして、その結果を文字列に変換し、さらにその文字列を反転させてみます。

const double = (n) => n * 2;
const toString = (n) => n.toString();
const reverse = (str) => str.split("").reverse().join("");

const result = 5 |> double |> toString |> reverse;
console.log(result); // "01"

Babelを使えば、このようにパイプライン演算子を使ったコードを問題なく実行できます。

関数の適用順序が一目瞭然なので、とてもわかりやすいコードになりましたね。

○サンプルコード3:複数の関数を合成する例

パイプライン演算子の真価は、もっと複雑な関数合成の場面で発揮されます。

例えば、ユーザーの入力をバリデーションして、整形して、データベースに保存するような一連の処理を考えてみましょう。

const validateInput = (input) => {
  // バリデーションロジック
  return input;
};

const sanitizeInput = (input) => {
  // 整形ロジック
  return input;
};

const saveToDatabase = (input) => {
  // データベース保存ロジック
  return input;
};

const processInput = (input) => 
  input 
    |> validateInput 
    |> sanitizeInput 
    |> saveToDatabase;

const result = processInput("Some input");
console.log(result); // "Some input"

パイプライン演算子を使えば、このような一連の処理をシンプルに表現できます。

inputデータが各関数を順に通過していく様子が見た目からも明らかです。

関数合成が頻出するようなコードでは、パイプライン演算子が大活躍してくれるはずです。

○サンプルコード4:配列メソッドとの組み合わせ

パイプライン演算子は配列メソッドともうまく組み合わせられます。

配列の要素を順に処理していくような場面では、パイプライン演算子を使うことで、コードの見通しが格段に良くなります。

const numbers = [1, 2, 3, 4, 5];

const result = numbers
  |> (arr => arr.filter(n => n % 2 === 0))
  |> (arr => arr.map(n => n * 2))
  |> (arr => arr.reduce((acc, n) => acc + n, 0));

console.log(result); // 20

この例では、numbersという配列を、偶数のみにフィルタリングし、それぞれの要素を2倍にし、最後にそれらを合計するという一連の処理を行っています。

配列メソッドとパイプライン演算子を組み合わせることで、データの流れが一目瞭然になりましたね。

●TypeScriptでパイプライン演算子を活用する

さて、ここまでJavaScriptでのパイプライン演算子の使い方を見てきましたが、もっと安全にパイプライン演算子を使う方法があります。

それが、型安全な言語であるTypeScriptです。

TypeScriptを使えば、パイプライン演算子をより安心して利用できるようになるんです。

○TypeScriptの型安全性とパイプライン演算子

TypeScriptは、JavaScriptに静的型付けを追加した言語です。

コンパイル時に型チェックを行うことで、実行時エラーを未然に防ぐことができます。

この型安全性は、パイプライン演算子を使う上でも大きなメリットになります。

例えば、次のようなコードを考えてみましょう。

const add = (a: number, b: number) => a + b;
const toFixed = (n: number, fractionDigits: number) => n.toFixed(fractionDigits);

const result = 1.2345 |> add(2) |> toFixed(2);
console.log(result); // "3.23"

このコードでは、addとtoFixedの引数の型が明示されています。

もしaddの引数に数値以外を渡そうとすれば、TypeScriptがコンパイル時エラーを出してくれます。

このように、TypeScriptを使えば、パイプライン演算子を使った関数合成をより安全に行えるようになるんです。

○サンプルコード5:TypeScriptでのパイプライン演算子の使用例

では、もう少し実用的な例を見てみましょう。

ユーザーの入力を受け取り、それを整形してデータベースに保存するような処理を考えます。

type User = {
  id: number;
  name: string;
  email: string;
};

const validateEmail = (email: string) => {
  // メールアドレスのバリデーションロジック
  return email;
};

const normalizeUser = (user: User) => {
  // ユーザーデータの正規化ロジック
  return user;
};

const saveToDatabase = (user: User) => {
  // データベース保存ロジック
  return user;
};

const processUser = (user: User) =>
  user
    |> normalizeUser
    |> (user => ({ ...user, email: validateEmail(user.email) }))
    |> saveToDatabase;

const user = {
  id: 1,
  name: "John Doe",
  email: "john.doe@example.com",
};

const result = processUser(user);
console.log(result); // { id: 1, name: "John Doe", email: "john.doe@example.com" }

ここでは、ユーザーデータを表すUserという型を定義しています。

そして、メールアドレスのバリデーション、ユーザーデータの正規化、データベースへの保存という一連の処理をパイプライン演算子で繋げています。

TypeScriptの型システムのおかげで、各関数の入力と出力の型が明確になり、安全にデータを処理できています。

○サンプルコード6:ジェネリック関数との組み合わせ

パイプライン演算子は、ジェネリック関数とも相性が良いです。

ジェネリック関数を使えば、様々な型に対して汎用的な処理を行える関数を作ることができます。

const filter = <T>(predicate: (value: T) => boolean) => (array: T[]) =>
  array.filter(predicate);

const map = <T, U>(transform: (value: T) => U) => (array: T[]) =>
  array.map(transform);

const reduce = <T, U>(reducer: (acc: U, value: T) => U, initialValue: U) => (array: T[]) =>
  array.reduce(reducer, initialValue);

const numbers = [1, 2, 3, 4, 5];

const result = numbers
  |> filter((n) => n % 2 === 0)
  |> map((n) => n * 2)
  |> reduce((acc, n) => acc + n, 0);

console.log(result); // 20

この例では、filter、map、reduceという3つのジェネリック関数を定義しています。

これらの関数は、配列の型Tとは独立に定義されているので、様々な型の配列に対して適用できます。

そして、パイプライン演算子を使ってこれらの関数を合成することで、配列の処理をシンプルに表現できています。

●ライブラリとの連携

JavaScriptでパイプライン演算子を使うことで、関数合成がとてもスムーズになりました。

でも、実はパイプライン演算子と相性の良いライブラリがあるんです。

それが、RxJSやfp-tsといった関数型プログラミングのライブラリです。

これらのライブラリと組み合わせることで、パイプライン演算子の真価がさらに発揮されるんですよ。

○RxJSのpipeオペレータとの比較

RxJSには、pipeオペレータという関数合成のためのオペレータが用意されています。

これは、パイプライン演算子と似たような役割を果たします。

例えば、次のようなコードを見てみましょう。

import { of } from 'rxjs';
import { filter, map } from 'rxjs/operators';

const source$ = of(1, 2, 3, 4, 5);

source$.pipe(
  filter(x => x % 2 === 0),
  map(x => x * 2)
).subscribe(console.log);
// 4
// 8

ここでは、RxJSのpipeオペレータを使って、filterとmapを合成しています。

これは、パイプライン演算子を使った以下のコードとほぼ同じ意味になります。

const source = [1, 2, 3, 4, 5];

const result = source
  |> (arr => arr.filter(x => x % 2 === 0))
  |> (arr => arr.map(x => x * 2));

console.log(result); // [4, 8]

RxJSのpipeオペレータは、ストリームの流れの中で関数合成を行うのに対し、パイプライン演算子は通常の値に対して関数合成を行うという違いがあります。

状況に応じて使い分けると良いでしょう。

○サンプルコード7:RxJSとパイプライン演算子の併用

実は、RxJSとパイプライン演算子を一緒に使うこともできるんです。

次のコードを見てみましょう。

import { of } from 'rxjs';
import { filter, map } from 'rxjs/operators';

const doubleEven = (source$) =>
  source$ |> ($ => $.pipe(
    filter(x => x % 2 === 0),
    map(x => x * 2)
  ));

const source$ = of(1, 2, 3, 4, 5);

doubleEven(source$).subscribe(console.log);
// 4
// 8

ここでは、doubleEvenという関数を定義しています。

この関数は、パイプライン演算子を使ってRxJSのpipeオペレータを適用しています。

こうすることで、ストリームに対する処理をより関数型っぽく書くことができます。

RxJSとパイプライン演算子、それぞれの良さを活かしたコードが書けるようになるんです。

○fp-tsを使った関数型プログラミング

fp-tsは、TypeScriptで関数型プログラミングを行うためのライブラリです。

不変データ構造やモナド、関数合成など、関数型プログラミングに必要な機能が豊富に用意されています。

パイプライン演算子と組み合わせることで、より簡潔で表現力の高いコードが書けるようになります。

○サンプルコード8:fp-tsとパイプライン演算子によるデータ変換

では、fp-tsとパイプライン演算子を使って、データ変換を行ってみましょう。

ユーザーデータを変換する例を見てみましょう。

import { pipe } from 'fp-ts/function';
import { map } from 'fp-ts/Array';

type User = {
  id: number;
  name: string;
  age: number;
};

const users: User[] = [
  { id: 1, name: 'Alice', age: 20 },
  { id: 2, name: 'Bob', age: 30 },
  { id: 3, name: 'Charlie', age: 25 },
];

const result = users |> pipe(
  map(user => ({ ...user, age: user.age + 1 })),
  map(user => `${user.name} (${user.age})`)
);

console.log(result); 
// ["Alice (21)", "Bob (31)", "Charlie (26)"]

ここでは、まずユーザーの年齢を1歳ずつ増やし、その後ユーザーの名前と年齢を文字列に変換しています。

fp-tsのpipe関数を使うことで、パイプライン演算子による関数合成とfp-tsの関数を組み合わせられます。

データ変換の流れが一目瞭然で、とても読みやすいコードになりました。

●よくあるエラーと対処法

パイプライン演算子を使っていると、時々エラーに遭遇することがあります。でも慌てる必要はありません。

エラーの原因を理解して、適切に対処すれば、すぐに解決できるはずです。

ここでは、パイプライン演算子を使う上でよく遭遇するエラーと、その対処法を見ていきましょう。

○構文エラー:パイプライン演算子の記述ミス

パイプライン演算子を使い始めたばかりの頃は、構文エラーを起こしがちです。

特に、パイプライン演算子の記号である”|>”を”|”と”>”に分けて書いてしまったり、関数の呼び出しを括弧で囲み忘れたりすることが多いようです。

例えば、次のようなコードは構文エラーになります。

const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

// 構文エラー:"|>"を"|"と">"に分けて書いている
const result = 1 | > add(2) | > multiply(3);

正しくは、次のように書きます。

const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

const result = 1 |> add(2) |> multiply(3);
console.log(result); // 9

パイプライン演算子の構文に慣れるまでは、ゆっくり丁寧にコードを書くことを心がけましょう。

エラーメッセージをしっかり読んで、構文を見直すことが大切です。

○型エラー:関数の型不一致

TypeScriptを使っていると、型エラーに悩まされることがあります。

パイプライン演算子を使った関数合成でも、関数の入力と出力の型が合っていないと、型エラーが発生します。

例えば、次のようなコードは型エラーになります。

const addOne = (n: number) => n + 1;
const greet = (name: string) => `Hello, ${name}!`;

// 型エラー:addOneの出力はnumber型だが、greetの入力はstring型
const result = "Alice" |> addOne |> greet;

addOne関数はnumber型の値を受け取ってnumber型の値を返すのに対し、greet関数はstring型の値を受け取ってstring型の値を返します。

そのため、addOneの出力をgreetに渡すことはできません。

型エラーを避けるには、関数の型をしっかりと定義し、型の流れを意識してコードを書くことが大切です。

必要であれば、型アサーションを使って型を合わせることもできます。

○パフォーマンス問題:関数合成の最適化

パイプライン演算子を使って多くの関数を合成すると、パフォーマンスが気になることがあります。

特に、大量のデータを処理する場合は、関数合成によるオーバーヘッドが無視できなくなることがあります。

例えば、次のようなコードは非効率的です。

const double = (n) => n * 2;
const square = (n) => n * n;
const addOne = (n) => n + 1;

const numbers = [1, 2, 3, 4, 5];

const result = numbers.map((n) => n |> double |> square |> addOne);
console.log(result); // [5, 17, 37, 65, 101]

このコードでは、配列の各要素に対して、double、square、addOneの3つの関数が順番に適用されています。

しかし、これらの関数は別々に呼び出されるため、関数呼び出しのオーバーヘッドが大きくなります。

パフォーマンスを改善するには、関数合成を手動で行うことができます。

const doubleSquareAddOne = (n) => (n * 2) * (n * 2) + 1;

const numbers = [1, 2, 3, 4, 5];

const result = numbers.map(doubleSquareAddOne);
console.log(result); // [5, 17, 37, 65, 101]

このように、関数を合成した新しい関数を作ることで、関数呼び出しのオーバーヘッドを減らすことができます。

パフォーマンスが重要な場面では、このような最適化を意識することが大切です。

●パイプライン演算子の応用例

さて、ここまでパイプライン演算子の基本的な使い方から、TypeScriptやライブラリとの連携まで見てきました。

でも、パイプライン演算子の真価は、実際のアプリケーション開発の中で発揮されるんです。

ここからは、パイプライン演算子の応用例をいくつか見ていきましょう。

きっと、パイプライン演算子の可能性の広さに驚かれるはずですよ。

○サンプルコード9:ログイン認証処理への適用

まずは、ログイン認証処理にパイプライン演算子を適用してみましょう。

ユーザー名とパスワードを受け取り、認証結果に応じてメッセージを返す処理を考えます。

const validateUsername = (username) => {
  // ユーザー名のバリデーションロジック
  return username;
};

const validatePassword = (password) => {
  // パスワードのバリデーションロジック
  return password;
};

const authenticate = (username, password) => {
  // 認証ロジック
  return username === 'admin' && password === 'password';
};

const createSuccessMessage = (username) => `ようこそ、${username}さん!`;
const createFailureMessage = () => 'ユーザー名かパスワードが間違っています。';

const login = (username, password) =>
  [username, password]
    |> (([username, password]) => [validateUsername(username), validatePassword(password)])
    |> (([username, password]) => authenticate(username, password) ? createSuccessMessage(username) : createFailureMessage());

const result1 = login('admin', 'password');
console.log(result1); // "ようこそ、adminさん!"

const result2 = login('guest', 'password');
console.log(result2); // "ユーザー名かパスワードが間違っています。"

ここでは、ユーザー名とパスワードのバリデーション、認証、メッセージ生成という一連の処理をパイプライン演算子で繋げています。

関数合成のおかげで、処理の流れがとてもわかりやすくなりましたね。

○サンプルコード10:データのバリデーションと整形

次に、データのバリデーションと整形にパイプライン演算子を使ってみましょう。

ユーザーの入力データをチェックし、適切な形式に変換する処理を考えます。

const validateName = (name) => {
  if (name.length < 2) {
    throw new Error('名前は2文字以上で入力してください。');
  }
  return name;
};

const validateEmail = (email) => {
  if (!email.includes('@')) {
    throw new Error('メールアドレスが正しくありません。');
  }
  return email;
};

const validateAge = (age) => {
  if (isNaN(age) || age < 0) {
    throw new Error('年齢は正の数で入力してください。');
  }
  return age;
};

const normalizeData = (data) => ({
  ...data,
  name: data.name.trim(),
  email: data.email.trim().toLowerCase(),
  age: Number(data.age),
});

const processData = (data) =>
  data
    |> validateName(data.name)
    |> validateEmail(data.email)
    |> validateAge(data.age)
    |> normalizeData;

const data = {
  name: 'John ',
  email: 'John@example.com',
  age: '30',
};

const result = processData(data);
console.log(result); 
// {name: "John", email: "john@example.com", age: 30}

このコードでは、名前、メールアドレス、年齢のバリデーションと、データの正規化をパイプライン演算子で表現しています。

バリデーションエラーがなければ、最後にnormalizeData関数でデータを整形します。

入力データの流れがわかりやすく、コードの意図が明確になりました。

○サンプルコード11:APIレスポンスの変換と加工

APIのレスポンスデータを変換し、加工する処理にもパイプライン演算子は活用できます。

ここでは、ユーザーデータを取得し、必要なデータを抽出する例を解説します。

const fetchUser = async (userId) => {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const user = await response.json();
  return user;
};

const extractUserData = (user) => ({
  id: user.id,
  name: `${user.firstName} ${user.lastName}`,
  email: user.email,
});

const hideEmail = (user) => ({
  ...user,
  email: user.email.replace(/.*@/, '****@'),
});

const processUser = (userId) =>
  userId
    |> fetchUser
    |> extractUserData
    |> hideEmail;

const userId = 123;

processUser(userId).then((user) => {
  console.log(user);
  // {id: 123, name: "John Doe", email: "****@example.com"}
});

ここでは、ユーザーIDをもとにAPIからユーザーデータを取得し、必要なデータを抽出して、メールアドレスを隠すという一連の処理をパイプライン演算子で表現しています。

非同期処理であるfetchUser関数も、パイプライン演算子の中で自然に使えています。

○サンプルコード12:UIイベントハンドラへの応用

最後に、UIイベントハンドラにパイプライン演算子を使った例を見てみましょう。

ユーザーの入力をバリデーションし、結果に応じてメッセージを表示する処理です。

<input type="text" id="name-input" />
<div id="message"></div>
const validateName = (name) => {
  if (name.length < 2) {
    throw new Error('名前は2文字以上で入力してください。');
  }
  return name;
};

const showSuccessMessage = (name) => `ようこそ、${name}さん!`;
const showErrorMessage = (error) => error.message;

const handleInput = (event) =>
  event.target.value
    |> validateName
    |> showSuccessMessage
    |> (message => {
        document.getElementById('message').textContent = message;
      })
    |> (error => {
        document.getElementById('message').textContent = showErrorMessage(error);
      });

document.getElementById('name-input').addEventListener('blur', handleInput);

このコードでは、ユーザーの入力をバリデーションし、結果に応じてメッセージを表示しています。

パイプライン演算子を使うことで、入力値の流れがわかりやすくなり、エラーハンドリングも自然に組み込むことができました。

まとめ

さて、JavaScriptのパイプライン演算子についてたっぷりと解説してきました。

これからは、みなさんの手でパイプライン演算子を自由自在に使いこなしていってください。

きっとJavaScriptプログラミングがもっと面白くなるはずです。

素晴らしいコードを書いていきましょう!

それでは!