- Day 12:Function Typeで「関数そのものを扱う」感覚を身につける
- Function Typeとは:関数にも「型」をつける
- 関数を変数に代入する:計算関数を「切り替え可能」にする
- 関数を引数に渡す:電卓関数を作る
- 関数を返す:計算方法を選ぶ「ファクトリ関数」
- 実践:計算方法を切り替える電卓を作る
- Day 12 練習テンプレート
- Day 12のまとめ
- Day 12:独自のFunction Typeを3種類作って「関数の型」を体になじませる
- Function Typeの基本:関数にも「型」をつける
- 独自の関数型1:StringTransformer(文字列変換)
- 独自の関数型2:BinaryNumberOp(数値計算)
- 独自の関数型3:UserLogger(オブジェクトを扱う)
- Day 12 練習テンプレート(3種類の独自関数型)
- Day 12のまとめ
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;
TypeScriptBinaryOperationという名前の「関数型」を定義しています。(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"));
TypeScriptDay 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);
}
TypeScriptDay 12のまとめ
Day 12では、
- Function Typeで「関数にも型をつける」考え方
- 独自の関数型を3種類(文字列変換・数値計算・ユーザーログ)定義する練習
- 関数を変数に代入して、あとから差し替えるテクニック
- 関数を引数に渡して、「処理の一部を外から指定する」スタイル
- 関数を戻り値として返して、「モードに応じて処理を選ぶ」ファクトリ関数
を一通り体験しました。
今日作った3種類の関数型は、ぜひ引数や戻り値の型を変えたり、処理内容を増やしたりしながら、自分なりの「関数型コレクション」に育ててみてください。 関数そのものを「データ」として扱えるようになると、プログラムの設計の自由度が一気に広がっていきます。
