プログラミング言語入門です。
僕が、パソコンについて思うことは、パソコンとは、すなわちプログラミングとは、言語とデータとなるリソースである、ということです。
CPUとメモリは、機械語の命令しか実行できません。これが、自然な素の状態のパソコンです。
ですが、パソコンはプログラミングをすることで、機械語の命令を「たくさんの高度な方法で応用」することで、さまざまなことを可能にします。それは、まるで「万能機械」あるいは「万能工場」のようなものです。
では、プログラミングとはなんでしょうか。それは言語です。
パソコンでは、言語を使って、プログラムやデータを記述します。もっとも簡単な言語は、何もないプレーンテキストです。これは、UNIXではテキストデータの形式として使われます。iniファイルやレジストリを使うシステムもあります。
データが記述できたところで、プログラムはそのデータを「入出力と環境に従って自動的にデータの操作・処理を行う」ということをします。
このプログラムを記述するために使われるのが、C言語などをはじめとするプログラミング言語です。
プログラミング言語には、今でも使われる主なものとして、FORTRAN、COBOL、Lisp、Pascal、C、C++、Java、C#、Perl、PHP、Python、Ruby、JavaScriptなどがあります。
プログラミング言語は、大きく分けて二つに分けられます。それは、プログラムを記述したらコンパイラという専用の翻訳プログラムを使って、カーネルの上で実行できるネイティブな機械語の実行形式のバイナリファイルを吐き出す「コンパイル言語」と、プログラムを機械語に翻訳するのではなく、インタープリタという専用の実行プログラムを使って、ひとつひとつの命令をインタープリタが解釈してインタープリタの上でプログラムを実行する「インタープリタ言語」です。
コンパイル言語の特徴は、コンパイルするためにコンパイラが必要なこと、WindowsやLinux向けのネイティブな実行ファイルが吐き出されるため、プログラムを動かすためにランタイムやVMを必要としないことです。C/C++はコンパイル言語です。
これに対してインタープリタ言語の特徴は、コンパイルなしでスクリプトを記述したらすぐさま実行できること、その代わり実行するためにインタープリタが環境に導入されていなければ実行できないことです。Perl, PHP, Python, Rubyなどはインタープリタ言語です。
また、この二つの中間のような言語に、JavaやC#のような「バイトコード言語」があります。JavaやC#のコンパイラは、機械語の実行ファイルを吐き出すことをせず、VM(仮想マシン)向けのバイトコードを吐き出します。そして、このバイトコードをVM上で実行します。
バイトコード言語では、コンパイルも必要であり、実行するためにはVMが必要であるという特徴がありますが、この二つの特徴によって、「一度コンパイルしたバイトコードをどの環境でも再コンパイルすることなく実行できる」という、優れた移植性のメリットを持っています。
なぜ移植性が重要なのか、それはコンパイラにはプログラミングしやすい言語で書くという以外に、「どんなCPUアーキテクチャに対しても、ひとつのソースコードからたくさんのCPU向けの機械語を吐く」という目的があるからです。コンパイラを使うことで、C/C++からでも、再コンパイルをすれば、IntelだろうとPowerPCだろうと、原理上は動く機械語コードを吐き出すことが可能です。しかしながら、C/C++では、多くの場合プラットフォーム依存のライブラリやシステムコールを使ったり、再コンパイルが必要だったりと、移植性には多くの問題があります。Javaは、その解決を目指して生まれたのです。
また、実際のところ、プログラミング言語だけが言語ではありません。HTMLやXMLをはじめとする、マークアップ言語があるからです。マークアップ言語は、文書やドキュメントデータを表現するのに極めて重要です。もし、HTMLをプログラミング言語のひとつであるとみなすなら、WebブラウザであるGoogle ChromeやMozilla Firefoxはある意味でHTMLの言語処理系であると言えます。もしそうでないとしても、WebブラウザにはJavaScriptの実行エンジンが含まれているため、どちらにせよ言語処理系です。XMLは、多くの高度なアプリケーションで使われているデータ形式のひとつで、HTMLの親戚のようなものです。
そう、わたしたちがユーザーインターフェースを持ったウィンドウだと思っていたものは、ほとんどが言語によってできています。プログラミング言語、マークアップ言語、あるいはデータ記述言語やプレーンテキストとった「言語」を処理し、GUIのウィンドウやコマンドラインの入出力、文書あるいはグラフィカルなビューなどといった「情報」に変換し、GUIのツールキットやWebのフォーム、ターミナルやキーボード入力から「操作」する、これがパソコンであると言っていいと思います。
簡単に言えば、パソコンは言語と情報です。言語を処理しながら情報を表示あるいは与えてやること、これがパソコンなのです。
そして、言語を扱うためには、素のCPUだけではそのCPUに基づく機械語しか実行できません。コンパイラあるいはインタープリタを作ることで、はじめてC言語のような「プログラマが書く言語」でプログラミングができます。コンパイラやインタープリタは、主に字句解析、構文解析、中間コード作成、最適化、コード生成といったフェースを踏んでC言語のような言語を機械語に変換します。以下にその手順を書きましたが、まだまったくIT技術の詳しい知識がない自分が書いたものを後で手直ししたため、分かる人が見るとおかしな点があるかもしれません。それを踏まえた上で、あえて純粋なその発想の価値を残すためにここにそれを掲載しています。
しかしながら、そもそもコンパイラとはなんなのでしょうか。
プログラミング言語を使わずにアセンブリ言語を使う場合、機械語に対応したニーモニックを書かなければいけません。
ですが、機械語やアセンブラでプログラムを書くのは至難の業です。電卓に記憶とジャンプ機能をつけたような、「とても低レベルな言語」で書かなければならないためです。
プログラミング言語を使えば、条件分岐、変数定義、関数、演算子などを用いて、人間の理解しやすい「流れるようなプログラム」を書くことができます。
また、コンピュータは、情報処理を行う機械です。この情報とは、0か1かの1ビットの情報の羅列であり、たとえば8ビットなら00001111などとなります。これによって、数値や文字などの値、演算命令、記憶の場所(メモリアドレス)などを表します。
また、コンピュータはジャンプ命令によって別の場所にある命令位置にジャンプすることができます。
ですが、それでも、プログラマが人間として行うべき部分はあります。それは、命令文の構築です。命令文をどのように構築するか、順番や実行内容をどのように記述するか、ということはプログラマが書かなければなりません。
ですが、プログラマが書かなければならないと言っても、プログラマが書くべき内容を、単なる機械語の機械的な羅列ではなく、「もっと抽象的な数式あるいは数学者の頭の中に近いコード」を書けるようになると、プログラムを書くのがはるかに楽になるでしょう。
プログラミング言語とは、そのような「人間が機械語ではなく数学者の頭に近いコードを書くことができる仕組み」であると言えます。そして、コンパイラを作る、ということは、すなわち「プログラミング言語を作る」ということに相当します。コンパイラは、人間を機械から数学へと戻し、「本来のすべきことに専念することができる」というツールなのです。
コンパイラがやっていることは、プログラミング言語で書かれた「論理的な形式のプログラム」を、機械語で書かれた「機械で実行可能な形式のプログラム」に変換するということです。
ここで、どちらのプログラムであっても、動きや処理の内容が変わることはありません。
ですが、論理的な形式のプログラムは、人間に読みやすく書きやすい形式であり、ソースコードと呼ばれます。
そして、機械で実行可能な形式のプログラムは、実際のハードウェア装置であるCPUが読むことのできる形式で、バイナリ形式と呼ばれます。
コンパイラを使ってソースコードからバイナリ形式に変換することで、人間が読みやすくまた書きやすい形式でプログラムを書き、それを実行内容はそのままで機械で実行できる形式に変換することができます。
コンパイラには世代があります。
まず、最初のコンピュータは機械語を直接記述してプログラミングを行いました。今でも、コンピュータが何もない素の状態で読み取ることのできるのは、この機械語だけです。
次に、アセンブリ言語が生まれました。これはいわば機械語の命令と人間に読みやすいキーワード(ニーモニック)を一対一で対応付けたもので、機械語の数値の命令を覚えなくても、人間が覚えやすい言葉で命令を記述することができます。
現代的なコンピュータの世界から言って、機械語とアセンブリ言語はほとんど同じものです。単に命令が数値なのか言葉なのかが違うだけにすぎません。
これに対して、FORTRANやCOBOLなどのプログラミング言語、すなわち「高級言語」が生まれます。これは、プログラムを単なるCPUの読み取ることのできる「機械的命令の羅列」ではなく、人間の読みやすく書きやすい「高級」な言語として書くことができるという意味で、高級言語と呼ばれます。
高級言語では、機械よりも数式に近い形でプログラムを記述することができますが、それだけではCPUが読み取って実行することができません。そのため、コンパイラあるいはインタープリタが必要となります。
コンパイラは、高級言語のプログラム全体を機械語やアセンブリ言語にあらかじめ全部変換しておいた上で実行します。インタープリタは、高級言語のプログラムを一行一行読み取って、一行ごとに機械語に変換して実行します。
高級言語は、時代とともに進歩してきました。最初に生まれた高級言語であるFORTRANや、古くから長い間使われ続け、今でもメインフレームなどで使われるCOBOLは、GOTO文によるジャンプを用いていましたが、GOTO文は複雑で保守性の悪いスパゲッティプログラムを生み出します。これに対して、CやPascalなどの構造化言語では、構造化プログラミングと呼ばれる考え方を取り入れ、順次実行、条件分岐(選択)、繰り返し(反復)の構文を、GOTO文の代わりに使うことができるようになりました。現在では、構造化以前の高級言語を使うのは、メインフレームの古いプログラムを保守する場合など特別な場合に限定され、多くが構造化プログラミング以降の言語を使います。なので、FORTRANやCOBOLは一般的には「古い言語」とされ、CやPascalはそれらに比べれば「新しい言語」とされます。
そして、構造化プログラミング言語よりも進歩した最近の言語では、オブジェクト指向言語と呼ばれる言語が生まれました。これはGOTO文と同様に保守性の悪いプログラムを生み出す「グローバル変数」をできるだけ撤廃するとともに、その代わりにオブジェクトと呼ばれる単位でプログラムの部分的なパッケージングを行います。主に共通して使われるデータを中心として、そのデータを操作する一連の関数的機能(メソッド)をデータ構造の中に埋め込む形で、「クラス」として設計・実装し、クラスに基づく「オブジェクト」を使ってパッケージングされたプログラムを利用するという、新しいパラダイムを実現した言語です。
オブジェクト指向言語は、Smalltalk、C++、Javaなどが有名です。
また、これらのパラダイムとは別に、静的型付け言語と動的型付け言語があります。静的型付け言語は、データの「型」を指定しなければいけない言語です。データには、数値以外にも文字列や論理値(真あるいは偽)などが存在しますが、これらのデータの種類を「型」として明示的に記述しなければならない言語を静的型付け言語と呼びます。C/C++やJavaは静的型付け言語です。これに対して、プログラムを記述する際には型をあまり意識しなくても、インタープリタができるだけ自動的に適切に変換して扱ってくれる言語のことを、動的型付け言語と言います。これはPerlやPHP、Python、Rubyなどの「スクリプト言語」に多く見られます。スクリプト言語とは「簡単かつ手軽に使える言語」という意味です(英語のスクリプトの直訳は「手書き」で、いわば手書きのようにその場でさっと書いて実行できる言語のこと)。
また、これらのほかに「関数型言語」という言語があります。関数型言語とは、関数型プログラミングをサポートする言語のことで、Lisp、Haskell、MLなどが例として挙げられます。これに対して、CやPascalのような言語は手続き型プログラミングと呼ばれます。関数型プログラミングにおいては、変数と関数の明確な区別がない、変数は代入ではなく束縛される、破壊的操作を嫌うなどの特徴があります。なかでも際立って異なるのはLispで、S式と呼ばれるリスト形式の構文の中で、言語の機能のほとんどが関数やマクロによって行われます。Lispを見ていると、「関数によってプログラミングを行うとはこういうことだ」という、ある種の「プログラミング言語のひとつの到達地点」を見ることができます。
これらのプログラミング言語の特徴を比較するのに、よく「高水準・低水準」という言葉が使われます。簡単に言えば、高水準とは「数学や数式に近い」ということで、低水準とは「ハードウェアや機械に近い」ということです。FORTRANやCOBOLのようなとても古い言語を除いて、現在使われる一般的な言語で言えば、低水準のプログラミング言語の代表格はC言語であり、高水準のプログラミング言語の代表格はLispのような関数型言語あるいはPythonやRubyのような動的スクリプト言語です。
一般的に、低水準言語の方が、ハードウェアに近い自由な記述ができて、スピードや効率性が高い(速度が速い)代わり、書くのが面倒くさくて、初心者には難しいという特徴があります。また、高水準言語の方が、抽象的かつ人間に理解しやすい記述ができて、初心者に優しい代わり、ハードウェアに近い記述が制限されており、スピードや効率が低い(速度が遅い)という特徴があります。特にPythonは非常に簡単に開発ができる代わりC言語に比べてスピードがとても遅いという特徴があります。ですが、スピードや効率の遅さを差し置いても、Pythonの生産性の高さは非常に魅力的です。このような場合、C言語でコアの部分を書いた上で、C言語とPythonとのインターフェースを作り、PythonからC言語で書かれたコア部分を使うことで、Pythonのスピードの遅さを少しばかり解決することができます。
Perl, PHP, Python, Rubyのような、手続き型の動的スクリプト言語は、「手軽に使えて使いやすい」ために、大いにIT業界で、特にWebのような生産性の高さや使いやすさ・手軽さを重視するエンジニアやデザイナーの中で使われています。
ですが、これらのお手軽言語は、プログラミング言語の技術的な特徴から見ると、本質的に作る意味がありません。
なぜなら、機械に近い処理はアセンブラやC/C++を使えばいいのであり、数学的な論理を重視した場合、関数型言語であるLispやHaskellを使ったほうが、プログラミングの本質的に言って正しいのです。
プログラミング言語的に分類した時、必要なのは「機械に近い記述ができる手続き型言語」と、「数式に近い記述が出来る関数型言語」です。要するに、ここで言いたいのは「手続き型ならC/C++を使い、関数型ならLispやHaskellを使えばいい」ということです。
ある意味、Javaには特殊な利用価値があります。それはプラットフォームを選ばない移植性、オブジェクト指向言語としての大規模なプログラミングにおける生産性と保守性、洗練されたクラスライブラリ、そしてガーベッジコレクションによる安全性などの「きちんと開発するための手段が揃っている」という、「お仕事でプログラミングをやる」という使いやすさです。
ですが、Perl, PHP, Python, Rubyのような言語はどうでしょうか。これらの言語は、言ってしまえばC/C++よりは分かりやすく、LispやHaskellよりは手順的であり、関数型のサポートをしていながら基本は手続き型であり、「使いやすい」という以外に使う意味がありません。
PythonやRubyでは、メモリ管理を手動で行うことも、静的に型を指定することもできません。動的な記述が要求されます。しかしながら、if文で条件分岐をしたりfor文で逐次的に繰り返しを行うのはC/C++のスタイルであり、完全に「goto式」から抜け出せていません。
そのため、PythonやRubyのユーザーは「簡単にプログラミングができる」という「簡単さ」ばかりを謳いますし、これらの言語は「使いやすさ」や「使い勝手の良さ」を重視しています。それは、これらの言語には簡単さや使いやすさしか、これらの言語を使うメリットがないからです。
今でこそ、WebにはPHPやRubyを使い、AI・人工知能や機械学習にはPythonを使うという「主戦場」ができましたが、以前のPythonやRubyは、簡単かつ使いやすいということばかりで、関数型プログラミングを忠実に守るわけでもなく、かといってC/C++のようにポインタやメモリの管理が手動でできるわけでもないという、「中途半端さ」が目立ちました。
もちろん、PythonやRubyが完全に「不要な言語」であるとは言えませんが、それでも、「賢い人間はC/C++とLispが分かる」というところは、今でも変わっていないのかなと思います。
C言語でchar型やint型をきちんと明記し、それらのポインタを文法として記述する理由は、「アセンブリへの変換」をする際に都合が良いからである。C言語はほとんどが「アセンブラ++」のようなもので、このため、低水準言語と呼ばれる。逆に、C言語を用いて書かれたJavaやRubyなどの言語は、高水準言語(高級言語)と呼ばれる。アセンブラや機械語と比較する場合は、C言語を高級言語と呼ぶ場合もある。Javaはオブジェクト指向の他ガーベッジコレクションと呼ばれる自動メモリ管理の機能もあり、「一度書けばどの環境でも動く」という標語があるように、移植性が高い。よって、通常はC言語を使うよりも、C++やJavaなどの高級言語を使った方が、生産的で、セキュアである。だが、一方、C言語は性能が高く、スピードが速い。ゲーム開発のように高速な描画処理を求められる場合には、現在でもC/C++が使われる。また、OSのようにハードウェアの操作をしなければならない場合にも、C言語やアセンブリ言語は今でも使われる。だが、Web開発のように生産性が求められる場合は、JavaやRubyなどを使うべきである。Cには安全性の問題もある。gets()などの関数を使わないようにしなければならない。
昔は、OSのようなハードウェア寄りのプログラムは、全部機械語とアセンブリ言語で書いていた。だが、C言語を作ったUNIXのプログラマたちは、もっと汎用的な記述をした方が、OSの移植性が高いことに気が付いた。汎用的な言語で処理を書き、それをそれぞれのCPUの機械語に翻訳すれば良い。そういうわけで、UNIXはC言語で書かれた最初のOSである。AT&Tが独占禁止法の中でパブリックドメインでソースコードを公開したことで、C言語のUNIXは当時大学や研究機関などに爆発的に広まった。えてして、コマンドやネットワーク処理はUNIXのものが理想的であり、最も安定している、ということが広まった。だが、Windowsの登場とともに、それも変わった。今や、世界は完全にWindowsの世界である。C/C++も、WindowsのVisual Studioが使われるのが普通になった。GNUはUNIX互換のフリーOSをまだ頑張っているが、GPLを採用している時点で流行らないだろう。ここから先、ITはどうなるのかは分からない。だが、オープンソースの世界で、PerlやPythonなどの発展があったように、その世界ではその世界なりの発展や進歩がまだ続いている。これからは、そうした、土壌の安定する「安定期」となるだろう。
本当は、C言語でOSを書くと、必ず「コンパイル」と言う作業が必要になる。コンパイルしたCPUでなければ、当然ながら動かない。Javaは、ここでひとつ問題の解決を図った。つまり、仮想マシン(VM)の上で中間コード(バイトコード)が動くようにした。こうすることで、クロスコンパイルのようなことをしなくても、サーバーやデスクトップやモバイルなどの環境で、同じ中間コードを動かすことができる。また、本当は、インタプリタ式の言語にしても、この問題はいくらか解決できる。WindowsのRubyで書いたコードを、LinuxのRubyで動かすことはできる。これはWebサーバーなどで良くやる手法である。
本当は、高水準言語だからといって、必ずしも低水準言語よりも勝っているわけではない。特に、スピードや性能の面で言うと、Javaは本当にC++よりもはるかに遅い。それに、最近のC++は機能が増えて、JavaやRubyにも劣らないほど賢い言語になっている。それから、C言語が動くプラットフォームは多い。それに、C言語では何も書けないかというと、そんなことはなくて、逆に、WindowsのAPIを使ったりする場合では、Rubyなんかよりもはるかに軽く動くネイティブのGUIを使うことができる。C#/VB.NETはまだまだ遅い。いつまでも、C/C++だけは残り続けるだろう。ジェネリックプログラミングのような要素は、C++を今でも最先端の言語仕様にたらしめている。
昔はUNIXがもっとも安定したIBM以外のプラットフォームだったが、現在はそうでもない。WindowsのIISも安定してきている。それに、時代はコマンドのOSではない。UNIXはそのうち廃れて、別のものに替わっていくだろう。IBMのメインフレームやCOBOLなどと全く同じである。もう、UNIXの時代ではなくなりつつある。
その一例として、最近は「Windows Subsystem for Linux」というものがある。Windowsカーネルの上でLinuxのプログラムを動かすことのできる互換レイヤーである。Cygwinのようなものだと思えば良いが、本当はMicrosoftがじきじきに対応した、というところがポイントである。わざわざ、Linuxなんかインストールしなくても、Windowsの上で全てのLinuxコンポーネントをネイティブに動かせる。
だが、一方ではLinuxはまだ頑張っている。その例がDockerである。Dockerでは、Linuxカーネルの上で「Linuxの上でLinuxを動かしているように」、ホストのLinuxカーネルの上で「コンテナプロセス」を動かすことができる。ここでは、DockerはLinuxの上でLinuxが動く。この点、性能やスピードが高い。逆に、Windowsを動かしたりすることはできない。
本当は、昔からそういうものはWineやMono、最近ではMSの.NET Coreのようにどんどん増えては消え去っていくが、理想としてMSのやっていることは正しい。最近、どう見てもLinuxよりもWindowsの方が安定してきつつあるからである。Linuxもそろそろ店じまいかもしれない。Windowsとの対決はいつかどちらかが勝つのか、あるいはいつまでもMSが勝ち続けて、Linuxの存在感がなくなっていくのか、そういう状況である。AndroidなどのGoogleの援軍もあるが、さすがにGNOMEやChromeにGoogleのサーバーとの連携機能をつけるのはやりすぎかもしれない。