第1章: 関数型プログラミングの基礎¶
📚 この章で学ぶこと¶
- 🤔 関数型プログラミングって何?
- 🔄 今までの書き方(命令型)との違い
- 💡 なぜTypeScriptで学ぶといいの?
- 🔒 データを変えない書き方(イミュータビリティ)
🎯 関数型プログラミングとは?¶
関数型プログラミング(Functional Programming、略してFP)は、プログラムを「関数の組み合わせ」で作る考え方です。
🍳 料理で例えると...¶
想像してください。あなたがケーキを作るとき:
- 従来の方法:ボウルの中で材料を直接混ぜる(データを直接変更)
- 関数型の方法:材料ごとに新しいボウルを使う(新しいデータを作成)
📋 主な特徴¶
-
関数を自由に扱える
関数を変数に入れたり、他の関数に渡したりできます -
データを変更しない(イミュータビリティ)
元のデータはそのまま、新しいデータを作ります -
関数の外に影響を与えない(副作用なし)
関数は計算だけして、画面表示やファイル保存はしません -
「何をするか」を書く(宣言的)
「どうやってするか」ではなく「何をしたいか」を書きます
🔄 今までの書き方(命令型)vs 関数型¶
同じ問題を2つの方法で解いて、違いを見てみましょう!
📊 例: 商品の価格を合計する¶
// 商品の価格の配列
const prices = [100, 250, 300, 150, 500];
function calculateTotal(prices: number[]): number {
let total = 0; // 合計を入れる箱を用意
// 1つずつ取り出して...
for (let i = 0; i < prices.length; i++) {
total += prices[i]; // 箱の中身を更新!
}
return total;
}
const 合計金額 = calculateTotal(prices);
console.log(`合計: ${合計金額}円`); // 合計: 1300円
reduceの説明
reduce
は配列の各要素を順番に処理して、1つの結果にまとめる関数です。
🤔 違いを整理してみると...¶
今までの書き方(命令型) | 関数型の書き方 | |
---|---|---|
何を書く? | 「どうやって」計算するか | 「何を」したいか |
データの扱い | 変数を更新していく | 新しい値を作る |
ループ | for文で一つずつ処理 | reduceでまとめて処理 |
コードの長さ | 長い | 短い |
どちらが良いの?
関数型の書き方は: - 👍 コードが短くて読みやすい - 👍 バグが入りにくい(変数を変更しないから) - 👍 テストが書きやすい
💪 TypeScriptで学ぶメリット¶
1. 🔍 型があるからミスに気づきやすい¶
// TypeScriptだと、間違いがすぐわかる!
const prices: number[] = [100, 200, 300];
// ✗ これはエラーになる(文字列を足そうとしてる!)
prices.reduce((total, price) => total + "円", 0);
// ✓ 正しい書き方
prices.reduce((total, price) => total + price, 0);
2. 🌏 実際の開発でも使える¶
TypeScriptは多くの企業で使われています。ここで学んだことは、そのまま仕事でも活用できます!
3. 🎯 少しずつ始められる¶
// 最初はこんなコードでも...
let total = 0;
for (const price of prices) {
total += price;
}
// 徐々に関数型に変えていける!
const total = prices.reduce((sum, price) => sum + price, 0);
🔒 データを変えない書き方(イミュータビリティ)¶
イミュータビリティ(不変性)とは、「一度作ったデータを変更しない」というルールです。
😟 データを直接変える書き方(避けたい)¶
// 🛍️ ECサイトのユーザー情報
const users = [
{ id: 1, name: "Alice", age: 25, points: 100 },
{ id: 2, name: "Bob", age: 30, points: 200 }
];
// ✗ 危険:元のデータを直接変更!
users[0].points = 150; // Aliceのポイントを直接変更
console.log(users[0].points); // 150 - 元のデータが変わってしまった!
// 💢 問題:いつ、どこで、誰が変えたかわからない...
🌱 新しいデータを作る書き方(推奨)¶
// 🛍️ ECサイトのユーザー情報
const users = [
{ id: 1, name: "Alice", age: 25, points: 100 },
{ id: 2, name: "Bob", age: 30, points: 200 }
];
// ✓ 安全:新しい配列を作成!
const updatedUsers = users.map(user =>
user.id === 1
? { ...user, points: 150 } // Aliceのポイントだけ変えた新しいオブジェクト
: user // 他のユーザーはそのまま
);
console.log(users[0].points); // 100 - 元のデータは安全!
console.log(updatedUsers[0].points); // 150 - 新しいデータに変更が反映
スプレッド構文(...)の説明
🎉 データを変えないメリット¶
1. 🔍 バグを見つけやすい¶
// データの変更履歴が明確
const 初期データ = { total: 1000 };
const 割引後 = { ...initialData, total: 900 };
const 税込み = { ...割引後, total: 990 };
// 各段階で何が起きたか一目瞭然!
2. 🤝 チーム開発が安心¶
// AさんとBさんが同時に作業しても大丈夫!
const ユーザーAの変更 = updateUserPoints(users, 1, 150);
const ユーザーBの変更 = updateUserAge(users, 2, 31);
// 元のusersは誰も変えていないので衝突しない
3. 🧪 テストが簡単¶
// 同じ入力なら必ず同じ結果
test('ポイント計算', () => {
const result = calculatePoints(100, 0.1);
expect(result).toBe(110); // 必ず110になる!
});
4. 🔄 「元に戻す」が簡単¶
💪 実践演習¶
📝 演習1: ショッピングカートに商品を追加¶
以下のコードを、データを変えない書き方に修正してください:
// 😰 元のカートを直接変更してしまうコード
type CartItem = { id: number; name: string; price: number };
function addToCart(cart: CartItem[], newItem: CartItem): CartItem[] {
cart.push(newItem); // 危険:元の配列を変更!
return cart;
}
ヒント1
push
は元の配列を変更します。代わりに新しい配列を作る方法を考えてみましょう。
ヒント2
スプレッド構文...
を使うと、配列の中身を展開できます。
解答
// 😀 安全:新しい配列を作成
function addToCart(cart: CartItem[], newItem: CartItem): CartItem[] {
return [...cart, newItem]; // 元のcartは変更されない!
}
// 使い方
const myCart = [{id: 1, name: "Apple", price: 100}];
const newCart = addToCart(myCart, {id: 2, name: "Orange", price: 150});
console.log(myCart.length); // 1 - 元のカートは変わらない
console.log(newCart.length); // 2 - 新しいカートに商品が追加
📝 演習2: セール商品の価格計算¶
以下のfor文を使ったコードを、関数型の書き方に変えてください:
// 割引対象商品(sale: true)の合計金額を計算
type Product = { name: string; price: number; sale: boolean };
function calculateSaleTotal(products: Product[]): number {
let total = 0;
for (const product of products) {
if (product.sale) {
total += product.price * 0.8; // 20%OFF
}
}
return total;
}
ヒント1
まずfilter
でセール商品だけを選びましょう。
ヒント2
次にmap
で各商品の割引後価格を計算しましょう。
ヒント3
最後にreduce
で合計を計算しましょう。
解答
// 🌱 関数型の書き方
function calculateSaleTotal(products: Product[]): number {
return products
.filter(product => product.sale) // 1. セール商品だけ選ぶ
.map(product => product.price * 0.8) // 2. 各商品の割引後価格
.reduce((total, price) => total + price, 0); // 3. 合計を計算
}
// または、1つのreduceでまとめて書くこともできます
function calculateSaleTotalV2(products: Product[]): number {
return products.reduce((total, product) => {
return product.sale ? total + (product.price * 0.8) : total;
}, 0);
}
📝 この章のまとめ¶
今日学んだことを振り返りましょう:
✅ わかったこと¶
- 関数型プログラミング = 関数を組み合わせてプログラムを作る方法
- イミュータビリティ = データを変更せず、新しいデータを作る
- 関数型の書き方のメリット:
- 🐛 バグが少ない
- 🧪 テストしやすい
- 👥 チームで開発しやすい
🔑 重要なキーワード¶
用語 | 意味 | 例 |
---|---|---|
イミュータブル | 変更できない | const newArray = [...oldArray, item] |
ミュータブル | 変更できる | array.push(item) |
スプレッド構文 | ... で展開 | {...obj, name: "新しい名前"} |
💡 実践のコツ¶
準備はできましたか?次は「純粋関数」という、もっと面白い概念を学びます!