Chatwork Creator's Note

ビジネスチャット「Chatwork」のエンジニアとデザイナーのブログです。

ビジネスチャット「Chatwork」のエンジニアとデザイナーのブログです。

読者になる

データ構造と代数構造への招待

みなさま、お疲れ様です!エンジニア採用広報の高瀬 (@Guvalif) です。

この記事は、Chatwork Advent Calendar 2020 における、16 日目の記事です。

Chatwork にはたくさんの部活動があるのですが、その中に「数学部」という部活があります。

この記事は、数学部の活動として定期的に実施していた社内圏論勉強会からスピンオフして、 「さまざまなデータ構造の背景にある、数学的な構造」を、わかりやすーく (≒ No ほむほむ*1に) 紹介してみるものです。

I. 参考文献のご紹介

まず本題に入る前に、この記事の元となった資料をご紹介します:

nineties.github.io

こちらは、@9_ties さんが 2013 年に実施していた圏論勉強会から、 第 3 回『様々な圏』より "自由対象" の章を抜き出したものです。

これら一連の資料はとても出来が良く、数学的厳密性を犠牲にすることなく、 関数型プログラミングが好きなエンジニアにとって身近な例がたくさん紹介されています。

一方で、ある程度専門的な数学知識を要求する部分もあり、 じっくりと考えて行間を埋めることも、時に必要となっているように思います。

この記事は、該当の章 (およびその周辺) のサーベイ記事を目指したものになります 👨‍🏫

II. データのまとまりとはなんだろう?

エンジニアであれば、複数のデータをまとめるために "配列"*2 を使ったことがあるでしょう。 しかし、これをきちんと数学的に定義しようとすると、意外と難しいことに気づきます。

「ん?集合を使えばデータのまとまりを表現できるのでは?? 🤔」と考えたあなた,次の例を見てみましょう:

  • 集合 \mathbb{X} を、配列と考えてみる
  • 集合 \mathbb{X} には、3 つの数値データ 0, 1, 2 が順番に収められているとする
  • Q. ここで、新たに数値データ 0 を集合 \mathbb{X} に追加すると、どうなるか?

f:id:cw-takase:20201215000935p:plain

われわれの勝手知ったる配列であれば、これは [ 0, 1, 2, 0 ] のようになって欲しいところです。 しかし残念なことに、数学的な集合 \mathbb{X} では [ 0, 1, 2 ] となってしまいます。

さらにいえば、[ 0, 1, 2 ][ 2, 1, 0 ] のような、収める順番が異なるもの同士も区別されません!

f:id:cw-takase:20201215000939p:plain

なぜそうなるのでしょうか?理由は次の通りです:

  1. 数学的な集合では、同一な要素は区別されず、1 つとして管理される
  2. 数学的な集合では、どの要素が含まれるかだけが重要であり、順番という概念は無い

III. 適切なモデルを考える

さて、数学的な集合をただ単に用いるだけでは、配列といったありふれたデータ構造でさえ、 表現できないことが明らかになりました 💀

しかし諦める必要はありません。適切な取り扱いができる限り、数学ではルールを自由に考えてよいのです! 🎉

というわけで、配列を数学的に扱うために、次のようなモデルを考えてみましょう:

  1. 任意のデータ x に対して、単一データのまとまりを |x| と表すことにする
  2. |x| のことを、"列" と呼ぶことにする
  3. 2 つの列 |x||y| に対して、連結操作 (演算子) \oplus を考える
  4. 連結結果は |x| \oplus |y| で表され、これもまた "列" であるとする

f:id:cw-takase:20201215000943p:plain

小難しい書き方をしましたが、ようするに数学的な表現で、なんらかデータを並べる方法を定義したと思えば良いです。

これも … |0|
あれも … |0| \oplus |1|
それも … (|0| \oplus |1|) \oplus |0|

どれもがデータの並びを表現していて、なおかつ集合という概念に頼っていないので、 同一の要素を区別してくれないとか、順番が無視されるということがありません。素晴らしいですね 😊

IV. 2 つの列は同じもの?

さて、列と連結操作という考え方により、複数のデータをうまく並べることができるようになりました。

ではここで、3 つの数値データ 0, 1, 2 が順番に収められている列を考えてみましょう:

  • (|0| \oplus |1|) \oplus |2|
  • |0| \oplus (|1| \oplus |2|)

… 2 つできてしまいました 😅

「ん?こんなのカッコを外したら一緒なのでは?? 🤔」と考えたあなた,そんなルールは与えていないのです。

  1. 任意のデータ x に対して、単一データのまとまりを |x| と表すことにする
  2. |x| のことを、"列" と呼ぶことにする
  3. 2 つの列 |x||y| に対して、連結操作 (演算子) \oplus を考える
  4. 連結結果は |x| \oplus |y| で表され、これもまた "列" であるとする

… どうでしょう?「カッコは自由に外していいよー」なんてルールはありませんね?*3

f:id:cw-takase:20201215172106p:plain

適切な取り扱いができる限り、数学ではルールを自由に考えてよいと先ほど言いましたが、 逆に言えばルールに無いことを勝手にしてはいけません 🙅‍♂️

しかし困りました。これらは 0, 1, 2 が順番に収められているという意味では同じに見えますから、 直感に従って同じと見なせるようにしたいですね。どうすれば良いのでしょうか? 😐💦

V. 連結操作に代数構造を加える

というわけで、列と連結操作だけでは、われわれの勝手知ったる配列を表現できないことが明らかになりました 💀

しかし諦める必要はありません。ルールを増やしてしまうのです! 🎉


■ 追加のルール*4

連結操作 (演算子) \oplus は、次の性質を満たすとする:

  • (|x| \oplus |y|) \oplus |z| = |x| \oplus (|y| \oplus |z|)
  • このような性質を、結合律と呼ぶ
  • 結合律のもとではカッコの有無は影響しないため、|x| \oplus |y| \oplus |z| のように表記してもよい

|x| \oplus |y| \oplus |z| という表記を [ x, y, z ] などと脳内変換してあげれば、 まったく普通の配列と同じ構造になったことがわかりますね 💪

… いえ、配列には空配列という、 何も含んでいないことを表す特別な構造が存在するのでした。であれば、さらにルールを増やしてみましょう:


■ さらに追加のルール*5

|| という特別な列が存在し、次の性質を満たすとする:

  • || \oplus |x| = |x|
  • |x| \oplus || = |x|
  • このような性質を、(左右) 単位律と呼ぶ
  • また、単位律をみたす特別な列 || のことを、(\oplus に対する) 単位元と呼ぶ

|| を空配列 [] と考えてあげれば、これで完全に配列の性質を表現できていると言えそうですね 💪

そして、このようになんらかの操作 (演算) に対して課されるルールのことを、 代数構造と呼びます。

VI. 代数構造を変えれば、データ構造も変わる

さて、列と連結操作に対して:

  • 結合律
  • 単位律

それぞれを導入することによって、配列の構造を再現することができました 👨‍🏫

ここで少し考察をしてみましょう。ルール (代数構造) を増やせば、 何か別のデータ構造が現れるのではないでしょうか?実際に試してみましょう。

■ 配列の構造に "可換律" を導入する

連結操作 (演算子) \oplus は、次の性質を満たすとする:

  • |x| \oplus |y| = |y| \oplus |x|
  • このような性質を、可換律と呼ぶ

可換律のもとでは、データの順番を入れ替えても、それぞれの列は同じと見なされます:

これも … |x| \oplus |y| \oplus |z|
あれも … |y| \oplus |x| \oplus |z|
それも … |x| \oplus |z| \oplus |y|
全て同じ!

すなわち、配列のようにデータを収めることができるが、順番は無視されるものですね。 これを一般的には MultiSet と呼ぶことが多いでしょう 📋

■ 配列の構造に "冪等律" を導入する

連結操作 (演算子) \oplus は、次の性質を満たすとする:

  • |x| \oplus |x| = |x|
  • このような性質を、冪等律と呼ぶ

冪等律のもとでは、同一のデータが連続しても、それらを省略して考えることができます:

これも … |x| \oplus |y| \oplus |y| \oplus |y| \oplus |z|
あれも … |x| \oplus |y| \oplus |y| \oplus |z|
それも … |x| \oplus |y| \oplus |z|
全て同じ!

すなわち、配列に対して連続重複除去を施したものですね。 これを ReactiveX などの流儀では DistinctUntilChanged と呼ぶことが多いでしょう 📋

■ 配列の構造に "可換律" と "冪等律" を導入する

最後に、連結操作 (演算子) \oplus が結合律,単位律,可換律,冪等律の全てを満たす場合を考えてみましょう。

ここでは |0| \oplus |1| \oplus |2||0| を連結してみます:

  1. |0| \oplus |1| \oplus |2| \oplus |0|
  2. |0| \oplus |0| \oplus |1| \oplus |2| (可換律を 2 回適用)
  3. |0| \oplus |1| \oplus |2| (冪等律を適用)

すなわち、既に存在するデータの重複が必ず除去され、なおかつ順番も無視できるものですね。 これは数学的な集合 (Set) の性質に、完全に一致しますね 📋

f:id:cw-takase:20201215135222p:plain

VII. まとめ

この記事では、「配列をいかに数学的に表すか?」というところからスタートして、 列と連結操作の導入,さらにそれに紐づく代数構造を考え、さまざまなデータ構造が表せることを見てきました。

実は、列と連結操作および代数構造というものを圏論で捉えると、 これらの結果はすべて "自由対象" という抽象化で表せたりします。 圏論は関数型プログラミングや型システムのみならず、データ構造にも繋がっていたんですねー。

Chatwork では他にも、Scala の勉強会,アジャイル・スクラムの勉強会,アルゴリズムの勉強会,etc …, 業務の一環としてさまざまな勉強会が実施されています。

日々研鑽を積みながら、チャレンジングな開発に挑戦していきたい方は、ぜひ弊社へ 💪

recruit.chatwork.com

*1:https://twitter.com/guvalif/status/1317719718002749441

*2:この記事では、可変長配列を考えています

*3:列と連結操作のみ考えたものを、数学用語では "自由マグマ" と呼びます

*4:列と連結操作に "結合律" を考えたものを、数学用語では "自由半群" と呼びます

*5:列と連結操作に "結合律" と "単位律" を考えたものを、数学用語では "自由モノイド" と呼びます