状態に関する世界観です。
プログラミングにおいて、状態をどのように保持するかということは重要な問題です。
たとえば、関数を実行するごとに1ずつカウントするプログラムや、行をひとつひとつ進めて処理を行うようなプログラムを考えてみます。
関数を実行した度に、その関数の内部のローカル変数は、(スタックに置かれたものについては)失われてしまいます。
どこか、関数の外部に、関数を実行し終わった後でも残るような、「状態」を残さなければなりません。
状態とは、このように局所的な関数だけではなく、プログラム全体としての実行内容を意味することもあります。
たとえば、telnetやsshのようなリモートログインソフトウェアを使う場合、コマンドをひとつひとつ実行しても、相手のホストとの接続状態は維持しておかなければいけません。
このように、プログラミングにおいて、プログラムの状態をどこかに確保しておくということは重要なことです。
状態を確保しないタイプのプログラムをステートレスと呼び、状態を確保するタイプのプログラムをステートフルと言います。
先に挙げた、telnetやsshのようなプログラムは、ステートフルなプログラムです。
逆に、ステートレスなプログラムの例は、Perl/CGIやPHPのようなCGIのようなプログラムです。
CGIにおいては、そのページの表示と通信ごとにデータはやり取りしますが、ページごとにデータの保持が完結しており、基本的には「ページをまたいだデータ」を持ちません。
ですが、CGIにおいても、Cookieやサーバーセッションなどを使うことで、ステートフルな状態保持をすることはできます。
後日注記:ステートフル・ステートレスについては、以下の書籍が参考になります。
CGIを参照のこと。
また、「永続化」と呼ばれる概念もあります。これは、プログラムが終了したとしても、メモリ上のデータをストレージ上に書き出す(永続化)することで、データをメモリから永続化データに移して残すことです。
たとえば、Javaのオブジェクトは、メモリ上に存在するため、プログラムが終了するか電源を切るかしてしまえば消えてしまうでしょう。
このようなデータは、RDBMSのようなデータベースにマッピング(O/Rマッピング)することで、ストレージ上に永続化することができます。
あるいは、ファイルをこのような永続化の目的に使うこともあります。UNIXでは、PATHの設定などはシェルを使ってもできますが、ログアウトすれば消えてしまうため、/etc以下にある設定ファイルやドットファイルを編集して恒久的に設定が残るようにします。
永続化とO/Rマッピングを参照のこと。
プログラムにおいて状態を保持するためには、いくらかの方法があります。
簡単で手軽なのは、グローバル変数を使う方法です。
どの関数からも参照されるような、グローバル変数を宣言し、それをどの関数からも参照するようにすれば、関数の呼び出しが終わってから次の関数が呼び出されたとしても、状態を保持することができます。
しかしながら、グローバル変数を使うことは推奨されません。理由は、「どこから変更されているのかが把握しづらくなる」ということと、「名前空間を汚染する」ということからです。
まず、グローバル変数を多用すると、その変数をプログラムのどこからでも書き換えられてしまいます。なので、バグが出て上手く動かなくなった時に、「どこからかけた変更が別のどこで参照されているのか」が分からなくなってしまい、保守性の低いプログラムになってしまいます。
次に、プログラムを複数人で作っている時に、それぞれが好き勝手に変数名をつけていると、衝突することがあります。別の人の「age」という変数と、自分の「age」という変数があった時に、それが混ざってしまうのです。
しかしながら、グローバル変数を使わなければならない言語も存在するでしょう。そのため、C言語ではstatc宣言をつけることで、そのファイル以外からは参照できないようにすることもできます。名前空間がある言語なら、別の名前空間とは別のグローバル変数であることを区別することもできます。
もし、そうした機能を使わないとしたら、たとえばWebプログラマが作るコードの変数はすべてに「web_」をつけ、データベースプログラマが作るコードの変数はすべてに「db_」をつける、といった方法でも対処は可能です。
C関連(2.制御フロー)やC言語(3.データ構造)を参照のこと。
一部の内容でANSI Common Lisp (スタンダードテキスト)を参考にしました。
ほかの方法として、グローバル変数と同じく手軽に使えるのは、関数の引数を使う方法です。
たとえば、実行するたびにカウントする関数は、引数に「今の値」を与え、返り値として「カウントした値」を返すようにすれば実現できます。
あるいは、ポインタを使って、引数として与えたデータを関数の内部から書き換えるようにし、参照型の「今の値」を与えるようにしても、実現できるでしょう。ポインタは値型の編集である必要はないため、構造体のポインタや参照型のオブジェクトを引数として与えることもでき、その構造体やオブジェクトを関数の中で操作してもよいでしょう。
これは、グローバル変数よりもよいやり方です。なぜなら、関数型プログラミングの方法を使っているからです。
関数型プログラミングとは、プログラムを「関数」で成り立たせる方法です。そして、関数型プログラミングでは、「非破壊的な操作」を重要視します。
破壊的な操作とは、既にある変数を書き換える方法のことです。非破壊的操作とは、変数を書き換えずに、新しい値の変数が必要になった場合に、その時その時の変数を新しく作って、関数の返り値として返す方法のことです。
一般的に、破壊的操作を行うよりも、非破壊的操作のほうが保守性の高いプログラムになります。破壊的操作をすべて禁止することではプログラムは書けないように思われるかもしれませんが、実際のプログラミングの場面では、破壊的操作が本当に必要な場合はごく少数であり、破壊的な操作を行う多くのプログラムを非破壊的なプログラムに置き換えることができます。
関数型プログラミングを参照のこと。
第三の方法として言えるのは、オブジェクト指向を用いる方法です。
オブジェクト指向は、すべてのメンバ関数(メソッド)から共通のデータにアクセスし、そのアクセスを制限したり、継承やポリモーフィズムを使って抽象的にプログラムを拡張することができます。
グローバル変数がスパゲッティプログラムになる原因は、「すべてのプログラム全体にデータの編集を可能にしている」という、いわばスコープの問題です。
オブジェクト指向では、関数すなわちプログラムを書き換える部分を、データの内部のメンバに加えることで、関数とデータのプロパティを同じものであると扱います。
それによって、プログラムはデータの一部となり、データという「基本単位」を基に、プログラムをそのデータをやり取りする「振る舞い」として記述します。
そして、グローバル変数は必要のないものとなります。グローバル変数はデータの中のメンバ変数となり、グローバル変数を書き換えるコードは同じデータの中のメンバ関数(メソッド)となるため、ひとつのクラスの中にそのオブジェクトに必要なすべてがある程度の大きさで分割されるのです。
オブジェクト指向は、さらにデータへのアクセス修飾子によって、データの安全な変更を可能とします。グローバル変数がどこからでも書き変わってバグが出る原因は、通常ならば行わないような不正な操作をどこかで行ったからです。アクセス修飾子を用いることで、そのような不正な操作は最初からできなくなります。すべて、正しい操作をメソッドやアクセサから行うように義務化することができます。
また、オブジェクト指向を使う場合、プログラムの再利用性が高まり、拡張が容易になります。基底クラスの機能を引き継いだ上で、派生クラスでプログラムに機能を付け足したり上書きしたりすること、すなわち「継承」が可能だからです。
オブジェクト指向やC言語(3.データ構造)を参照のこと。
オブジェクト指向は、きちんとしたプログラムの設計をする際、たとえば大規模なプログラミングを行う際に効果を発揮しますが、もっと小規模で手軽に行いたい場合は、クロージャを使うこともできます。
クロージャとは、関数の宣言と同じスコープにある外部の変数を、関数の中から参照する機能です。
たとえば、実行するたびにカウントを1ずつ増やすプログラムなどは、クロージャを用いることで、カウント変数var c = 0;を宣言したのと同じスコープに、function inct() { c = c + 1; }を宣言することで実現できます。
クロージャはとても便利な機能で、JavaScriptやLispなどに備わっています。関数だけで状態を持ちたいのであれば、クロージャを使うのがよいでしょう。
また、JavaScriptでは、関数は特定のクラスには束縛されません。なので、関数そのものの再利用が、オブジェクト指向のクラスを使う場合よりもやりやすいです。たとえば、先ほどのインクリメント関数をthis.c = c + 1;と書き換えるだけで、thisにどんなオブジェクトが与えられたとしてもそのオブジェクトのcを操作できます(this参照)。これを委譲と呼びます。
クロージャ・無名関数・関数オブジェクトやJavaScript(3.プロトタイプチェーン)を参照のこと。
状態遷移とは、制御モデルのひとつで、マシンが「状態」を持っていて、その状態がさまざまな値に変わることで、システム全体の動き方が変わるようなプログラムのことです。
この方法の一例として、僕の作ったロボットが参考になるかもしれません。
FluxはFacebookによって提唱された状態管理のモデルのことで、Redux(React)やVuex(Vue.js)などで実装されている。
一部の内容(グローバル変数の名前汚染の対処法、関数型プログラミングにおける破壊的操作と非破壊的操作の置き換え、オブジェクト指向におけるデータの中の振る舞いとしてのプログラムのあり方)について、以下の書籍を参考に執筆しました。
また、JavaScriptとクロージャについては以下の雑誌を参考に執筆しました。