JavaScript/TypeScriptメモ

これから何を学ぶのか? - most adequate guide Chapter 1のテキトーな訳

このページの趣旨

下記ページの、かなり「大雑把」に、「私が興味を持ったところだけ」を「雰囲気」で翻訳したものです。

mostly-adequate.gitbooks.io Chapter 01: What Ever Are We Doing?

Chapter1: これから何を学ぶのか?

カモメの群れの例で考える

カモメの例で考えてみよう。

  • 群れが合流したら大きな群れができる(※こちらの群れと、相手の群れの足し算)
  • 繁殖をしたら、交配した数だけ増える(※こちらの群れの数と、相手の群れの数のかけ算)

まずは、オブジェクト指向でコードを書くとこんな感じ。

これは、現代の代入ベースのプログラムの組み方に危険性に焦点をあてるための例です。

class Flock {
  constructor(n) {
    this.seagulls = n;
  }

  conjoin(other) {
    this.seagulls += other.seagulls;
    return this;
  }

  breed(other) {
    this.seagulls = this.seagulls * other.seagulls;
    return this;
  }
}

const flockA = new Flock(4);
const flockB = new Flock(2);
const flockC = new Flock(0);
const result = flockA
  .conjoin(flockC)
  .breed(flockB)
  .conjoin(flockA.breed(flockB))
  .seagulls;
// 32

意図としては、答えは16になってほしいのに、32になってしまっています。flcokAは、計算過程で値が変わってしまっているのが原因です。

もし、理解できなくても、それは全然問題ありません。私自身も理解できないので(笑)。

覚えておいてほしいのは、「状態」と「変化する値」を追うのは、大変だということです。実際、今回のような簡単な例でも大変ですよね?

関数型言語のアプローチで考えてみる

では、もう一度同じ問題をやってみましょう。

今度は関数型言語のアプローチでやってみます。

const conjoin = (flockX, flockY) => flockX + flockY;
const breed = (flockX, flockY) => flockX * flockY;

const flockA = 4;
const flockB = 2;
const flockC = 0;
const result =
    conjoin(breed(flockB, conjoin(flockA, flockC)), breed(flockA, flockB));
// 16

今度は正しい値が計算できたでしょう?しかも、短いコードで。もっとも、関数の入れ子は、少し読みにくいですが(これについては、Chapter 5でこの問題を解決します)。

最初の例に比べれば、かなり良いプログラムになったけれど、さらにプログラムを改良できないか考えてみましょう。先ほどの関数がどういうものか考えてみると、これらの関数は、単に足し算(conjoin)、かけ算(breed)をしているだけです。それなら、これらの関数を、足し算は足し算、掛け算は掛け算と呼んだほうがいいのではないか?

関数名を実態に合わせる

この2つの関数は、名前以外に特に変わっているところもないので、これらの関数をaddmultiplyに変えて、名前を実体に合わせてみましょう。

const add = (x, y) => x + y;
const multiply = (x, y) => x * y;

const flockA = 4;
const flockB = 2;
const flockC = 0;
const result =
    add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
// 16

上のように書きなおしたことで、次のような「昔の人の知恵」を利用することができます。

// 結合法則
add(add(x, y), z) === add(x, add(y, z));

// 交換法則
add(x, y) === add(y, x);

// 単位元「0」の存在
add(x, 0) === x;

// 分配法則
multiply(x, add(y,z)) === add(multiply(x, y), multiply(x, z));

数学の法則を使って、さらに簡単な式に書き換える

これらの昔から知られている数学的な性質を利用していきましょう。もし、これらの内容がわからなくても安心してください。ほとんどの人にとって、これらの算数の法則を学んでから、随分時間が経っているのですから。

それでは、これらの性質を使って、先ほどのプログラムを簡単な形に直していきましょう。

// 元々の形
add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));

// 単位元「0」の足し算をしても結果は変わらないので、取り除きましょう
// (add(flockA, flockC) == flockA)
add(multiply(flockB, flockA), multiply(flockA, flockB));

// 分配法則(の逆)を使って、最終的な形に変形しましょう
multiply(flockB, add(flockA, flockA));

この関数呼び出しだけで充分だったのです。あとは、add関数とmultiply関数の定義を書き足しておけば、完全なものになります。でも、実際には、これらの定義を各必要はありません。addmultiplyは、既存のライブラリで提供されているはずです。

このChapterのまとめ

ひょっとしたら、「こんな数学っぽい例を全面に出されても意味がない」「実際のプログラムは、こんなに単純じゃないから、こんな方法では無意味だ」などと考えているかもしれません。でも、この例を選んだのは、ほとんどの人が「足し算」と「掛け算」を理解しているからです。そして、算数の知識がどれだけ利用価値が高いかが簡単に理解できだと思います。

数学がわからないからといって悲観的になる必要もありません。この本の中では、圏論や集合論、ラムダ計算を使って、実世界ででてくる例を、プログラム化していきます。その結果は、さきほどのカモメの群れの例と同じように、綺麗・シンプルなプログラムができあがるでしょう。もちろん、あなたは数学の学者になる必要もありません。この本で扱うことは、自然に・簡単に感じられるでしょう。それは、一般的なフレームワークやAPIを使うのとあまり変わりません。

(以下、疲れたのと、本題に関係なさそうなので、翻訳は略)