Imaginantia

思ったことを書きます

概念「自由」

あるサークル内wikiに書いた「週刊『概念』」の1記事を持ってきました。自由の概念は誰もがよく使っているのできっと意義があるとおもいます。 前半数学寄り、後半プログラマ寄りの内容です。


圏論という分野において、「自由」という概念があります。これはまさしく "free" を体現します。 そしてそれは、いろんな分野において、うれしいことなのです。だって自由だしね。 「やりたいこと」そのものはそんなに難しいことではありません。

自由とは

数学における自由は、どちらかというと "free" の「タダ」が近いのかもしれません。 端的に言うと、「ある概念をあったことにする」ような操作のことです。

概念は所詮概念なので、具体的な操作はできません。でも、「自由」構成によってそれを具象化し、操作できるようにするのです。 そしてそれは後にほんとうの具象に落とし込みます。結果、概念を非常に純粋な形で操作できるのです。

具象には必ず概念とは離れた「何か」がくっついていますが、「自由」なものにはそれが含まれないのです。結晶みたいなもんですね。

基底と線形空間

以後、線形空間は有限次元とします。

ある線形空間があったとき、多分なんらかの基底が取れると思います。

逆に基底「だけ」があったとき、元の線形空間を復元できますか?

それは無理です。基底というのは所詮何か物体の集まりでしかなくて、本来の線形空間を体現する和やスカラー倍などの情報を知らなければ全く同じ空間にはなりません。

(1,0)(0,1) があったところで、その和が (1,1) であるとは限らないし、ましてや (x,y) の形式であるかすらもわからないのです。 物を区別するためには情報が必要です。 集合の元程度では何もわからないのです。


しかし!元の空間と同じ次元を持つ空間は再現できます。「これを基底とする空間があったことにする」のです。 二次元平面 \mathbb{R}^2 の標準基底 (1,0), (0,1) があるとき、元の空間の点は全て a(1,0) + b(0,1) として書けたはずです。 ならば! a(1,0) + b(0,1) として書いた「もの」全てを以って空間を構成してはどうでしょうか。 重要なのは、a(1,0) はこれ以上計算できないし、+ も計算できるものではないということです。所謂「形式的」ってやつですね。

形式的な表記をするのは、単に見やすいからです。実際のところ例えば \langle a,b\rangle でも全く問題が無いのです。

結局振る舞いは先程言った「ほんとうの具象に落とし込む」部分によって決まるのです。それまでは意義、目的が指定されてない。これが即ち自由であるということです。

この「再現された空間」には自動的に演算が定まっています。 a(1,0) + b(0,1) + c(1,0) + d(0,1) はどうなるべきか?もちろん (a+c)(1,0) + (b+d)(0,1) であるべきです。 では r \times (a(1,0) + b(0,1)) は? ra(1,0) + rb(0,1) であるはずですね。ならそうすればいいのです。 そうして出来たこの空間は確かに線形空間になってくれます。

線形空間という概念が強制する法則を「満たしたことにする」ことで、純粋な概念としての線形空間を実現できたのです。

ここまでの表現に、一切 (1,0)(0,1) の「内部構造」を利用してないことに注意してください。


そしてこの空間は所詮「形式的」なものです。もしも元の空間に「情報を戻す」ようなことがしたければ、そのように操作を行います。 形式的であった a(1,0) + b(0,1) を、元の空間でちゃんと解釈しなおすと、 (a,b) になりますね。

もしも \langle a,b\rangle として表現していたのなら、f(\langle a,b\rangle) = a(1,0) + b(0,1) とすれば良いですね。 ここでの右辺の加法とスカラー倍は元の空間における操作のことです。

というわけで、「何かの残り滓」から「自由な空間を構成」して「最後に元の空間に落とし込む」という流れができました。 これが有用である1つのケースは、自由な空間が単純になっていることがあることです。

ここでは線形空間が完全にただのユークリッド空間 \mathbb{R}^n になっていますね。

つまるところ「基底」がやりたいことそのまんまの話ですね。空間全体のことを知らずとも、十分な情報があれば十分に操作が出来るのです。

列 / 自由モノイド

総和をとったり最大値をとったりしたいことが (特にプログラミングで) あると思います。 例えば、1 + 2 + 3 + 4 = 10 とか、 3 \lor 5 \lor 6 \lor 4 = 6 とか。

ここでは a \lor b で大きい方をとる演算ということにしますね。

「複数の値について、全体に渡って演算を行う」というのはよくあるパターンだと思われます。そこでその構造を抜き出してみましょう。 私達はこのような演算を行う際、順序が定まっていること、もしくは計算順序が無関係であることを期待しているはずです。

100 - 4 - 3 - 2 - 1 みたいなこと、やらないですよね。やるとしても 100 は特別扱いだと思います。

そんな二項演算のことをモノイド (monoid) と言います。私達はモノイドを使って総和や最大値を取っています。

さて、ここで構造を抜き出す、すなわちモノイドがあることにするとどうなるでしょうか。方法は簡単で、演算子を知らなかったことにすればいいです。

第一回で言った「具体例を知らないこと」の実例ですね。

すると、1 \square 2 \square 3 \square 4 とか 3 \square 5 \square 6 \square 4 になります。これはモノイドの概念だけを体現した純粋な物体のはずです。 実際に具体例として +\lor を突っ込めば計算ができる。さっき (基底と線形空間の話) と同じような状況ですね。

そして私達はこの名前を知っています。ですね。すなわち、自由なモノイドとは、列のことです。 逆に言えば列とは自由モノイドのことです。自由モノイドらしく振る舞うのであれば、それはもはや列なのです。

列の値の総和が取りたいのであればそのためのモノイドを。 列の長さが取りたいならそのためのモノイドを。 列の最初の値が取りたいならそのためのモノイドを。 モノイドを切り替えることで列に対する様々な演算が実現されます。

私達は無意識に「自由であることの恩恵」を受けているのです。自由であるからこそ演算による畳み込み (fold) が出来ているのです。 この辺のさらなるお話を知りたい方は、「代数的データ型」もしくは「F-始代数」で検索するとたのしそうなおはなしが見えるかもしれません。

多項式

ちょっとしたおはなしなんですけど。

多項式あるじゃないですか。あれも自由ですよね。「x という値があったことにする」ことで生まれるものが多項式です。 x^2 + 3x + 2 と私達は書きますが、本質的には (...,0,0,1,3,2) と何ら変わらないはずです。 無限に続く係数列と無限に続く係数列に適切な演算が入っている。ただそれだけです。

そうやって決められた多項式に対して、あとから具体的な値を代入することができる。先程の現象と同じです。

「形式的」という言葉が (個人的に) ちょっと好きではないので、こういう形で表現できると嬉しいかなって思ったりするのです。

ドメイン固有言語、そして自由モナド

またプログラムの話ですけど。何か処理をしたい!って思うとき、その処理ってそんなに「広く」ないと思うんです。

広いなら分割しろっていう話でもあるんですけど。

というのは、例えば「キャラクタを前に進めて、1秒後に180度回転して、もう一度同じ動きをする」という処理には次の要素が含まれています。

  • キャラクタを前に進める
  • 処理を1秒待つ
  • キャラクタを180度回転する
  • 処理を繰り返す

十分な量の「単純な動作」があれば、それだけの組み合わせによって目的の動作が完遂できるはずです。 そしてきっと、その「単純な動作」以外をしてほしくない!っていう需要もあるはずです。

もしも位置を操作出来たら、超高速に移動してしまうかもしれない。 もしも壁裏に居るプレイヤーを認識できたら、超反応してくるAIが出来てしまうかもしれない。 それは望ましいことではないはずです。だから良い操作だけを組み合わせてプログラムを作る。

なら、もちろんその組み合わせを記述することによってプログラムができたら最高なのです。 そこで出てくる思想が、ドメイン固有言語 (Domain Specific Language, DSL) です。 名前の通り、目的に応じた言語を設計してその上でコードを書くことによって、不要なコードと偶発的なミスを無くすことが出来ます。

そしてちょっとした面白い特徴が、「書かれたコードを後に再解釈する」必要がある点です。どこかで聞いた話ですね。 最初に書かれた「キャラクタを前に進める」という記述はほんとうにただの記述で、何の意味 (semantics) も持ちません。 「キャラクタを前に進める」とはどういうことか、を誰かが書いて初めて実際に動作するはずです。

つまるところ、ドメイン固有言語という仕組みは単純な動作から「自由」に生成されたプログラムということになります。

では、どんな概念を具象化したものでしょうか。


「処理」というものそれそのものの性質として、最も重要だと考えられるのが 繋げる 機能です。 「ある処理」を行い、その次に「ある処理」を行う。これが処理の1つの本質的な要素です。 ではこれはモノイドか?というと、ちょっと足りません。実際のプログラムでは一個前の処理の結果によって次の処理内容自体を変えたりするからです。

体力が低い → ならば、回復 そうでもない → ならば、攻撃 みたいな。さらに「回復して待機、そして攻撃」みたいに複雑化・階層化してくるとモノイドでは手に負えません。

そこで出てくるのが、モナド (Monad) という概念です。モノイドっぽいけど、私達が思うような感じのモノイドではありません。

実は「階層化された処理」を単純形に落とし込むところがモノイドになってます。 そして「処理を繋げる機能」はその階層によって実現されています。

つまり、ドメイン固有言語という仕組みは 自由モナド みたいなものなのです。 逆に言えば自由モナドドメイン固有言語を生みます。実際にそうやって活用されていたりするのです。

(Haskellにおける) 自由モナドは、自然に(勝手に) 条件分岐や繰り返しができる構造になります。 わざわざ自作スクリプト言語に機能として条件分岐などを入れるまでもなく、それらがタダ (free!) で手に入るのですね。 Haskellの良いところはそういうところなのです。やりたいことだけを書き、その解釈部分を簡単に分離できる。

まぁこれは一解釈です。

自由の表現

「ある物体が自由であるか」は、実は簡単にわかるものではありません。 具体的には「任意の具象に落とし込んだときにうまくいくか」をチェックする必要があります。

でも、逆に言えば、「任意の具象に落とし込んだときにうまくいく」ならば「それは自由なモノ」なのです。 だから、ある概念に対してその「自由な具象」というものは、たくさんあります。

(1,0), (0,1) から生成される空間の元が a(1,0) + b(0,1) でも \langle a,b\rangle でも良いという話はしました。 そしてこれらは単純に比較することを考えれば「違うもの」のはずですね。

でもそれらはある意味で同一になります。本質的な部分だけを見れば、全く同じものになります。