Chapter 12

第十二章 ラムダ式を用いた具象クラスの簡略化

kuramapommel
kuramapommel
2021.05.07に更新

中学数学の f(x) をラムダ式で表してみる

中学生の頃、数学で関数というものに出会った時のことを覚えていますでしょうか

突然出てきた f(x) とかいう謎の表現
当時のぼくはこれの意味がよくわからなかったんですよね

f ってなんだ?? x ってなんだ??
当時数学の先生に質問したところ「数学でよく使われるおまじないみたいなものだよ」みたいな形で教えてもらった記憶があります

教えてもらってもよくわからなかったんですけど、プログラミングを始めてからなんとなく理解できました

たとえば

f(x) = x^2 - 5x + 1

みたいな関数は TypeScript だと下記のように記述できます

function f(x: number) {
  return x * x - (5 * x) + 1
}

つまり f(x) というのは x という名前の引数 を取る f という名前の関数 のことと捉えることができるでしょう
(数学専門じゃないので、間違えていたら教えてください)

で、この関数ですが TypeScript では アロー関数 という記法を使って下記のように記述することができます

// `x` を引数に取り、 `x * x - (5 * x) + 1` の結果を返すことを表したアロー関数
(x: number) => x * x - (5 * x) + 1

どことなく先の数式に形が似ていることに気がついてもらえると、書き方読み方を覚えやすいと思います
ここで注目したいのが、アロー関数にすると f つまり 関数の名前がなくなった ということです

これは名前のない関数であることから 無名関数 と呼びます
そして、アロー関数のような無名関数を実装する記法をプログラミングの世界では ラムダ式 と呼びます

ちなみに 関数にも型 があります

const calculate = (x: number) => x * x - (5 * x) + 1

この場合の calculate 変数の型は何になるでしょう
これがすごくややこしいんですけど、この変数の方は number 型の x という引数を取り、 number 型を返す関数なので (x: number) => number と表現します

型を添えて書き直してみると下記のようになります

// `: (x: number) => number` の部分が calculate の型を示している部分
const calculate: (x: number) => number = (x: number) => x * x - (5 * x) + 1

なんかもう = とか => とかめちゃくちゃ出てきてわけわからん!みたいになると思いますが : から = までは型を表している と覚えておくと読み書きできると思います

メソッドがひとつの interface はラムダ式に置き換えることができる件

11 章の Map ではポリモーフィズムを活かして異なる武器の factory を登録していましたね

const weaponFactoryMap: Map<WEAPON_TYPE, WeaponFactory> = new Map([
  [
    WEAPON_TYPE.LONG_SWORD, new LongSwordFactory()
  ],
  [
    WEAPON_TYPE.DUAL_BLADES, new DualBladesFactory()
  ],
  [
    WEAPON_TYPE.BOW, new BowFactory()
  ],
])

でも、 武器が増えていくたびに具象クラスの定義を追加していかないといけないのは面倒 だと感じませんか?
ぼくはめんどくさいです

そこでラムダ式の登場です

何故突然ラムダ式の話になったのかというと、実は 11 章の WeaponFactory はラムダ式に書き換えることができるからなんですね
WeaponFactory のように メソッドをひとつしか持たない型はラムダ式に置き換えることができる んです!

この章では interface とその具象クラスをラムダ式を用いてより簡単に記述する方法をご紹介したいと思います
まずは、 WeaponFactory の「責務」から考えていきましょう

interface WeaponFactory {
  create(): Weapon
}

WeaponFactory の責務は「武器を生成すること」ですね
つまり create メソッドのお仕事のことです

create メソッドは「引数を取らず Weapon を返す」メソッドですので、これを関数として表してみると

function create(): Weapon {
  // 実際は Weapon は interface なので、コンパイルエラーになります
  return new Weapon()
}

この関数は「引数を取らずに Weapon を返す関数」なので型で表すと () => Weapon となります
つまり WeaponFactory という型はその責務にだけ注目すると () => Weapon という型とほとんど同じ と言えます

これを LongSwordFactory , DualBladesFactory , BowFactory それぞれの create メソッドに当てはめてみましょう

const createLongSword: () => Weapon = () => new LongSword()
const createDualBlades: () => Weapon = () => new DualBlades()
const createBow: () => Weapon = () => new Bow()

このように LongSwordFactory , DualBladesFactory , BowFactory のそれぞれは 3 つのラムダ式に置き換えることができました
つまり元の Map は下記のように書き換えることができます

// 1. value の型に WeaponFactory ではなく `() => Weapon` を設定する
const weaponFactoryMap: Map<WEAPON_TYPE, () => Weapon> = new Map([
  [
    WEAPON_TYPE.LONG_SWORD, () => new LongSword()
  ],
  [
    WEAPON_TYPE.DUAL_BLADES, () => new DualBlades()
  ],
  [
    WEAPON_TYPE.BOW, () => new Bow()
  ],
])

使うときはこう

function createWeaponInstance(type: WEAPON_TYPE): Weapon {
  const create = weaponFactoryMap.get(type)
  // create は `() => Weapon` 型の関数なので、そのまま `()` で呼べる
  return create()
}

このようにしてメソッドをひとつしか持たない interface とその具象クラスは、ラムダ式に置き換えられることがご理解いただけたかと思います
こうなってしまえば WeaponFactory のような interface 定義も不要になりますし、武器が増える度に LongSwordFactory のような具象クラスの定義を増やす必要もない ですね
異なる振る舞いを追加したいときはラムダ式で簡単に済ませてしまえば良い ですから

ラムダ式は関数型プログラミングからの輸入品なのですが、面白いことにこれもポリモーフィズム、つまりはオブジェクト指向プログラミングのアプローチで説明できてしまいました  
ラムダ式はモダンな言語だともはや当たり前のように使われていて、覚えておいて絶対に損しないどころか、脱初学者のためには必修科目と呼べるものなので、必ず身につけておきましょう

ちなみに Java とかだとこのようなメソッドをひとつしか持たない interface のことを FunctionalInterface と呼びます
普段 Java 使っている方など、気になる方は調べてみてください