はじめてのTypeScriptプログラミング入門 30日コース | ObjectとFunction - Day 12:Function Type

30日で身につけるTypeScript
スポンサーリンク
スポンサーリンク

Day 12:Function Typeで「関数そのものを扱う」感覚を身につける

Day 12では、Function Type(関数の型)をテーマに進めていきます。 これまで「数値」や「文字列」、「配列」などを変数に入れてきましたが、今日は一歩進んで、関数そのものを変数に入れる・引数として渡す・戻り値として返すというテクニックを学びます。

実践テーマは、計算方法を切り替える電卓です。

  • add(足し算)
  • subtract(引き算)
  • multiply(掛け算)
  • divide(割り算)

これらを「関数の型」でまとめて扱いながら、電卓の計算方法を切り替えられるようにしていきます。

Function Typeとは:関数にも「型」をつける

関数の型を言葉で表す

まず、「関数の型」を言葉で表してみます。

「2つの数値を受け取って、数値を返す関数」

これをTypeScriptで書くと、次のようになります。

// 2つの number を受け取り、number を返す関数の型
type BinaryOperation = (a: number, b: number) => number;
TypeScript
  • BinaryOperation という名前の「関数型」を定義しています。
  • (a: number, b: number) => number が、関数の形(引数と戻り値の型)を表しています。

この型を使うことで、

  • 関数を変数に代入する
  • 関数を引数に渡す
  • 関数を戻り値として返す

といった操作を、型安全に行えるようになります。

関数を変数に代入する:計算関数を「切り替え可能」にする

計算関数を定義する

まずは、基本の計算関数を4つ用意します。

// 2つの number を受け取り、number を返す関数の型
type BinaryOperation = (a: number, b: number) => number;

// 足し算
const add: BinaryOperation = (a: number, b: number): number => {
  return a + b;
};

// 引き算
const subtract: BinaryOperation = (a: number, b: number): number => {
  return a - b;
};

// 掛け算
const multiply: BinaryOperation = (a: number, b: number): number => {
  return a * b;
};

// 割り算
const divide: BinaryOperation = (a: number, b: number): number => {
  if (b === 0) {
    console.log("エラー: 0 で割ることはできません");
    return NaN;
  }
  return a / b;
};
TypeScript

重要ポイント:

  • すべての関数が BinaryOperation 型になっています。
  • 「2つの数値を受け取って、数値を返す」という形が共通しているので、同じ型でまとめられます。
  • const add: BinaryOperation = ... のように書くことで、「この変数にはこの形の関数しか入らない」という保証が得られます。

関数を変数に代入して使う

次に、「どの計算を使うか」を変数で切り替えてみます。

// 現在の計算方法を表す変数
let currentOperation: BinaryOperation;

// 足し算を選択
currentOperation = add;
console.log("足し算:", currentOperation(10, 5)); // 15

// 引き算を選択
currentOperation = subtract;
console.log("引き算:", currentOperation(10, 5)); // 5

// 掛け算を選択
currentOperation = multiply;
console.log("掛け算:", currentOperation(10, 5)); // 50

// 割り算を選択
currentOperation = divide;
console.log("割り算:", currentOperation(10, 5)); // 2
TypeScript

深掘りポイント:

  • currentOperation は「今使う計算方法」を表す変数です。
  • 中身は関数ですが、呼び出し方は currentOperation(10, 5) のように、普通の関数と同じです。
  • 「関数を変数に入れておき、あとから差し替える」というスタイルは、電卓のような「モード切り替え」にぴったりです。

関数を引数に渡す:電卓関数を作る

電卓関数の設計

次に、「計算方法を引数として受け取る電卓関数」を作ってみます。

// 計算方法(関数)を引数として受け取る電卓関数
function calculate(a: number, b: number, operation: BinaryOperation): number {
  // operation は、add / subtract / multiply / divide のいずれか
  return operation(a, b);
}

// 実行例
console.log("calculate(add):", calculate(10, 5, add));           // 15
console.log("calculate(subtract):", calculate(10, 5, subtract)); // 5
console.log("calculate(multiply):", calculate(10, 5, multiply)); // 50
console.log("calculate(divide):", calculate(10, 5, divide));     // 2
TypeScript

重要ポイント:

  • operation: BinaryOperation が、「計算方法を表す関数」を引数として受け取っています。
  • calculate 関数は、「どの計算をするか」を自分では決めず、呼び出し側に任せています。
  • こうした「処理の一部を外から渡す」スタイルは、柔軟な設計につながります。

関数を返す:計算方法を選ぶ「ファクトリ関数」

関数を返す関数のイメージ

次は、「文字列で指定された計算方法に応じて、対応する関数を返す」関数を作ってみます。

// 計算方法の種類を表す文字列
type OperationName = "add" | "subtract" | "multiply" | "divide";

// 計算方法に応じて関数を返すファクトリ関数
function getOperation(name: OperationName): BinaryOperation {
  if (name === "add") {
    return add;
  } else if (name === "subtract") {
    return subtract;
  } else if (name === "multiply") {
    return multiply;
  } else {
    // "divide" の場合
    return divide;
  }
}

// 実行例
const op1 = getOperation("add");
console.log("getOperation('add'):", op1(10, 5)); // 15

const op2 = getOperation("multiply");
console.log("getOperation('multiply'):", op2(10, 5)); // 50
TypeScript

重要ポイント:

  • getOperation は、「文字列を受け取って、対応する計算関数を返す」関数です。
  • 戻り値の型が BinaryOperation になっていることで、「必ずこの形の関数が返ってくる」ことが保証されます。
  • こうした「関数を返す関数」は、設定やモードに応じて処理を切り替える場面でよく使われます。

実践:計算方法を切り替える電卓を作る

ここまでの要素を組み合わせて、計算方法を切り替えられる電卓プログラムを作ってみます。

電卓の設計方針

  • 計算方法は add / subtract / multiply / divide の4種類。
  • ユーザーが「どの計算をしたいか」を文字列で指定する。
  • その文字列に応じて、対応する関数を選び、計算を実行する。

電卓プログラムのコード例

// day12-calculator.ts
// Day 12:Function Type を使った電卓

type BinaryOperation = (a: number, b: number) => number;
type OperationName = "add" | "subtract" | "multiply" | "divide";

// 計算関数たち
const add: BinaryOperation = (a, b) => a + b;

const subtract: BinaryOperation = (a, b) => a - b;

const multiply: BinaryOperation = (a, b) => a * b;

const divide: BinaryOperation = (a, b) => {
  if (b === 0) {
    console.log("エラー: 0 で割ることはできません");
    return NaN;
  }
  return a / b;
};

// 計算方法に応じて関数を返す
function getOperation(name: OperationName): BinaryOperation {
  if (name === "add") {
    return add;
  } else if (name === "subtract") {
    return subtract;
  } else if (name === "multiply") {
    return multiply;
  } else {
    return divide;
  }
}

// 電卓関数:計算方法の名前で指定して計算する
function calculateWithName(
  a: number,
  b: number,
  name: OperationName
): number {
  const operation = getOperation(name); // 対応する関数を取得
  const result = operation(a, b);       // 実際に計算
  return result;
}

// テスト実行
console.log("=== 電卓テスト ===");

console.log("add:", calculateWithName(10, 5, "add"));           // 15
console.log("subtract:", calculateWithName(10, 5, "subtract")); // 5
console.log("multiply:", calculateWithName(10, 5, "multiply")); // 50
console.log("divide:", calculateWithName(10, 5, "divide"));     // 2
console.log("divide by 0:", calculateWithName(10, 0, "divide")); // NaN + エラーメッセージ
TypeScript

深掘りポイント:

  • OperationName"add" | "subtract" | "multiply" | "divide" のユニオン型にすることで、「あり得る値」を型で制限しています。
  • getOperation は、「文字列 → 関数」という対応関係を1か所にまとめています。
  • calculateWithName は、「名前で指定された計算方法を使って計算する」という高レベルな関数になっています。
  • 関数の型を使うことで、「計算方法を差し替えられる電卓」という設計が、自然なコードで表現できています。

Day 12 練習テンプレート

最後に、今日の内容をまとめた練習用テンプレートを載せておきます。 このままコピーして、計算方法を増やしたり、ログ出力を追加したりしながら、自分なりの電卓に育ててみてください。

// day12-template.ts
// Day 12:Function Type 練習テンプレート

// 関数の型
type BinaryOperation = (a: number, b: number) => number;
type OperationName = "add" | "subtract" | "multiply" | "divide";

// 計算関数
const add: BinaryOperation = (a, b) => a + b;
const subtract: BinaryOperation = (a, b) => a - b;
const multiply: BinaryOperation = (a, b) => a * b;
const divide: BinaryOperation = (a, b) => {
  if (b === 0) {
    console.log("エラー: 0 で割ることはできません");
    return NaN;
  }
  return a / b;
};

// 関数を返す関数
function getOperation(name: OperationName): BinaryOperation {
  if (name === "add") {
    return add;
  } else if (name === "subtract") {
    return subtract;
  } else if (name === "multiply") {
    return multiply;
  } else {
    return divide;
  }
}

// 関数を引数に渡す電卓
function calculate(a: number, b: number, operation: BinaryOperation): number {
  return operation(a, b);
}

// 名前で指定する電卓
function calculateWithName(a: number, b: number, name: OperationName): number {
  const operation = getOperation(name);
  return operation(a, b);
}

// テスト
console.log("=== Day 12 テスト ===");

console.log("calculate(add):", calculate(10, 5, add));
console.log("calculate(subtract):", calculate(10, 5, subtract));
console.log("calculate(multiply):", calculate(10, 5, multiply));
console.log("calculate(divide):", calculate(10, 5, divide));

console.log("calculateWithName(add):", calculateWithName(10, 5, "add"));
console.log("calculateWithName(multiply):", calculateWithName(10, 5, "multiply"));
console.log("calculateWithName(divide by 0):", calculateWithName(10, 0, "divide"));
TypeScript

Day 12のまとめ

Day 12では、

  • Function Typeで「関数にも型をつける」方法
  • 関数を変数に代入して、計算方法を切り替えるテクニック
  • 関数を引数に渡して、「処理の一部を外から指定する」スタイル
  • 関数を戻り値として返して、「設定に応じて処理を選ぶ」ファクトリ関数
  • それらを組み合わせた「計算方法を切り替える電卓」

を一通り体験しました。

関数そのものを「値」として扱えるようになると、プログラムの設計の自由度が一気に広がります。 今日の電卓コードは、ぜひ計算方法を増やしたり、ログを追加したりしながら、「関数の型を使うと設計がどう変わるか」を自分の目で確かめてみてください。


Day 12:独自のFunction Typeを3種類作って「関数の型」を体になじませる

Day 12の課題は、独自の関数型を3種類作ることです。 テーマはシンプルですが、ここをしっかり押さえておくと、後の「設計力」がぐっと変わってきます。

  • Function Type(関数の型)
  • 関数を変数に代入する
  • 関数を引数に渡す
  • 関数を返す

この3つを、自分で定義した関数型を使いながら、ステップバイステップで体験していきます。

Function Typeの基本:関数にも「型」をつける

関数の型を言葉でイメージする

まず、「関数の型」を言葉で表現してみます。

  • 「文字列を受け取って、文字列を返す関数」
  • 「数値を2つ受け取って、数値を返す関数」
  • 「ユーザー情報を受け取って、何も返さずにログを出す関数」

これらは、TypeScriptでは次のように書けます。

// 文字列を受け取って文字列を返す関数の型
type StringTransformer = (text: string) => string;

// 数値を2つ受け取って数値を返す関数の型
type BinaryNumberOp = (a: number, b: number) => number;

// ユーザー情報を受け取って何も返さない関数の型
type UserLogger = (user: { name: string; age: number }) => void;
TypeScript

ポイント:

  • type 名前 = (引数...) => 戻り値の型; という形で、関数の「形」を型として定義できます。
  • こうしておくと、「この変数にはこの形の関数しか入らない」「この引数にはこの形の関数しか渡せない」といった制約がかかり、コードが安全で読みやすくなります。

ここからは、独自の関数型を3種類作り、それぞれで「変数に代入」「引数に渡す」「戻り値として返す」を体験していきます。

独自の関数型1:StringTransformer(文字列変換)

型定義:文字列を受け取って文字列を返す

// 文字列を受け取って文字列を返す関数の型
type StringTransformer = (text: string) => string;
TypeScript

この型は、

「1つの文字列を受け取り、変換した文字列を返す関数」

を表しています。

関数を変数に代入する

// 大文字に変換する関数
const toUpper: StringTransformer = (text: string): string => {
  return text.toUpperCase();
};

// 前後の空白を削除する関数
const trim: StringTransformer = (text: string): string => {
  return text.trim();
};

console.log(toUpper("hello"));      // "HELLO"
console.log(trim("  hello  "));     // "hello"
TypeScript

ポイント:

  • const toUpper: StringTransformer = ... のように書くことで、「この変数には StringTransformer 型の関数しか入らない」という保証が得られます。
  • 実際の呼び出しは toUpper("hello") のように、普通の関数と同じです。

関数を引数に渡す

// 文字列と変換関数を受け取って、変換結果を返す関数
function transformText(text: string, transformer: StringTransformer): string {
  // transformer は、toUpper や trim などの関数
  return transformer(text);
}

console.log(transformText("hello", toUpper)); // "HELLO"
console.log(transformText("  world  ", trim)); // "world"
TypeScript

ポイント:

  • transformer: StringTransformer が、「文字列変換をする関数」を引数として受け取っています。
  • transformText は、「どんな変換をするか」を自分では決めず、呼び出し側に任せています。

関数を返す

// モードに応じて文字列変換関数を返す
type TransformMode = "upper" | "trim";

function getTransformer(mode: TransformMode): StringTransformer {
  if (mode === "upper") {
    return toUpper;
  } else {
    return trim;
  }
}

const t1 = getTransformer("upper");
console.log(t1("hello")); // "HELLO"

const t2 = getTransformer("trim");
console.log(t2("  hello  ")); // "hello"
TypeScript

ポイント:

  • getTransformer は、「モードに応じて適切な変換関数を返す」関数です。
  • 戻り値の型が StringTransformer になっているので、「必ず文字列を受け取って文字列を返す関数が返ってくる」ことが保証されます。

独自の関数型2:BinaryNumberOp(数値計算)

型定義:数値を2つ受け取って数値を返す

// 数値を2つ受け取って数値を返す関数の型
type BinaryNumberOp = (a: number, b: number) => number;
TypeScript

この型は、

「2つの数値を受け取って、計算結果の数値を返す関数」

を表しています。

関数を変数に代入する

// 足し算
const add: BinaryNumberOp = (a: number, b: number): number => {
  return a + b;
};

// 引き算
const subtract: BinaryNumberOp = (a: number, b: number): number => {
  return a - b;
};

console.log(add(10, 5));      // 15
console.log(subtract(10, 5)); // 5
TypeScript

関数を引数に渡す

// 計算関数を受け取って実行する関数
function calculate(a: number, b: number, op: BinaryNumberOp): number {
  return op(a, b);
}

console.log(calculate(10, 5, add));      // 15
console.log(calculate(10, 5, subtract)); // 5
TypeScript

ポイント:

  • op: BinaryNumberOp が、「計算方法を表す関数」を引数として受け取っています。
  • calculate は、「どの計算をするか」を自分では決めず、呼び出し側に任せています。

関数を返す

// 計算方法の名前
type OpName = "add" | "subtract";

// 名前に応じて計算関数を返す
function getOp(name: OpName): BinaryNumberOp {
  if (name === "add") {
    return add;
  } else {
    return subtract;
  }
}

const op1 = getOp("add");
console.log(op1(10, 5)); // 15

const op2 = getOp("subtract");
console.log(op2(10, 5)); // 5
TypeScript

ポイント:

  • 「文字列 → 関数」という対応関係を1か所にまとめることで、コードの見通しがよくなります。
  • Function Typeを使うことで、「どんな関数が返ってくるか」が明確になります。

独自の関数型3:UserLogger(オブジェクトを扱う)

型定義:ユーザー情報を受け取ってログを出す

// ユーザー型
type User = {
  name: string;
  age: number;
};

// ユーザー情報を受け取って何も返さない関数の型
type UserLogger = (user: User) => void;
TypeScript

この型は、

「Userオブジェクトを受け取って、ログを出すなどの処理をするが、値は返さない関数」

を表しています。

関数を変数に代入する

// シンプルなログ出力
const simpleLogger: UserLogger = (user: User): void => {
  console.log(`ユーザー: ${user.name} (${user.age}歳)`);
};

// 詳細なログ出力
const detailedLogger: UserLogger = (user: User): void => {
  console.log("=== ユーザー情報 ===");
  console.log("名前:", user.name);
  console.log("年齢:", user.age);
};

const u: User = { name: "Taro", age: 20 };

simpleLogger(u);
detailedLogger(u);
TypeScript

関数を引数に渡す

// ユーザー配列とロガー関数を受け取って、全員分をログ出力する関数
function logUsers(users: User[], logger: UserLogger): void {
  for (const user of users) {
    logger(user);
  }
}

const users: User[] = [
  { name: "Taro", age: 20 },
  { name: "Hanako", age: 25 },
];

logUsers(users, simpleLogger);
logUsers(users, detailedLogger);
TypeScript

ポイント:

  • logger: UserLogger が、「ユーザーをどう表示するか」を表す関数を引数として受け取っています。
  • logUsers は、「誰を表示するか」だけを担当し、「どう表示するか」はロガー関数に任せています。

関数を返す

// モードに応じてロガー関数を返す
type LogMode = "simple" | "detail";

function getLogger(mode: LogMode): UserLogger {
  if (mode === "simple") {
    return simpleLogger;
  } else {
    return detailedLogger;
  }
}

const logger1 = getLogger("simple");
logger1({ name: "Jiro", age: 18 });

const logger2 = getLogger("detail");
logger2({ name: "Saburo", age: 30 });
TypeScript

ポイント:

  • 「モードに応じて処理を切り替える」場面で、関数を返すスタイルがとても自然に使えます。
  • Function Typeを使うことで、「返ってくる関数がどんな引数・戻り値を持つか」が明確になります。

Day 12 練習テンプレート(3種類の独自関数型)

最後に、今日の内容をまとめた練習用テンプレートを載せておきます。 このままコピーして、モードや処理内容を変えながら、自分なりの関数型を増やしてみてください。

// day12-custom-function-types.ts
// Day 12:独自の Function Type を3種類作る練習

// 1. StringTransformer
type StringTransformer = (text: string) => string;

const toUpper: StringTransformer = (text) => text.toUpperCase();
const trim: StringTransformer = (text) => text.trim();

function transformText(text: string, transformer: StringTransformer): string {
  return transformer(text);
}

// 2. BinaryNumberOp
type BinaryNumberOp = (a: number, b: number) => number;

const add: BinaryNumberOp = (a, b) => a + b;
const subtract: BinaryNumberOp = (a, b) => a - b;

type OpName = "add" | "subtract";

function getOp(name: OpName): BinaryNumberOp {
  if (name === "add") {
    return add;
  } else {
    return subtract;
  }
}

function calculate(a: number, b: number, op: BinaryNumberOp): number {
  return op(a, b);
}

// 3. UserLogger
type User = {
  name: string;
  age: number;
};

type UserLogger = (user: User) => void;

const simpleLogger: UserLogger = (user) => {
  console.log(`ユーザー: ${user.name} (${user.age}歳)`);
};

const detailedLogger: UserLogger = (user) => {
  console.log("=== ユーザー情報 ===");
  console.log("名前:", user.name);
  console.log("年齢:", user.age);
};

type LogMode = "simple" | "detail";

function getLogger(mode: LogMode): UserLogger {
  if (mode === "simple") {
    return simpleLogger;
  } else {
    return detailedLogger;
  }
}

// テスト実行
console.log("=== StringTransformer ===");
console.log(transformText("hello", toUpper));
console.log(transformText("  world  ", trim));

console.log("=== BinaryNumberOp ===");
console.log(calculate(10, 5, add));
console.log(calculate(10, 5, subtract));
console.log(getOp("add")(10, 5));

console.log("=== UserLogger ===");
const users: User[] = [
  { name: "Taro", age: 20 },
  { name: "Hanako", age: 25 },
];

const logger1 = getLogger("simple");
const logger2 = getLogger("detail");

for (const u of users) {
  logger1(u);
  logger2(u);
}
TypeScript

Day 12のまとめ

Day 12では、

  • Function Typeで「関数にも型をつける」考え方
  • 独自の関数型を3種類(文字列変換・数値計算・ユーザーログ)定義する練習
  • 関数を変数に代入して、あとから差し替えるテクニック
  • 関数を引数に渡して、「処理の一部を外から指定する」スタイル
  • 関数を戻り値として返して、「モードに応じて処理を選ぶ」ファクトリ関数

を一通り体験しました。

今日作った3種類の関数型は、ぜひ引数や戻り値の型を変えたり、処理内容を増やしたりしながら、自分なりの「関数型コレクション」に育ててみてください。 関数そのものを「データ」として扱えるようになると、プログラムの設計の自由度が一気に広がっていきます。

タイトルとURLをコピーしました