プログラミング言語入門です。
僕が、パソコンについて思うことは、パソコンとは、すなわちプログラミングとは、言語とデータとなるリソースである、ということです。
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のサーバーとの連携機能をつけるのはやりすぎかもしれない。
プログラミング言語の方式には、大別して、コンパイラ言語とインタープリタ言語の二つの方式がある。
コンパイラ言語の方式では、プログラムは実行される前に機械語の実行ファイルに翻訳される。それに対して、インタープリタ言語の方式では、プログラムをインタープリタが読み込んで実行しながら、逐次的に機械語に翻訳される。
どちらの方式であっても、プログラミング言語の定める文法のルールに従ってプログラムを書き、そのプログラムがその文法の通りに実行される。そして、その文法とルールは、マシンのCPU依存の機械語や、機械語に一対一でニーモニックと呼ばれる英語の命令が対応するアセンブリ言語(実質的に機械語と同等)に比べて、書きやすく、読みやすく、開発や保守管理もしやすい。
コンパイラ言語の代表格は、C/C++言語。コンパイラ言語の方式では、プログラムを書くだけではプログラムは動かない。コンパイラと呼ばれる、ソースコードを機械語のネイティブの実行ファイルに変換してから実行する。実行する前にコンパイラによる変換処理が終わっており、コンパイラによって最適化処理(プログラムの実行内容を変えることなく処理速度や効率などを向上させるための変換)も終わっているため、実行速度は速い。
コンパイラ言語には、静的型付けの言語が多い。静的型付けとは、変数を使う際に必ずプログラマによって明示的な変数の型を指定する必要のある言語のこと。
以下の内容は、C/C++言語のような手続き型言語に特有の特徴であり、同じコンパイラ言語(あるいはインタープリタ言語)であっても、Lispのような関数型言語では内容が異なる。
C言語は、プログラムを手続きのように書く必要がある。手続き型とは、「これをして、その次にこれをして、その次にこれをしなさい」というように、順序的かつ厳密な処理によってプログラムを記述する言語のこと。たとえば、「変数xに0を代入し、変数xをインクリメント(1ずつ増やす)し、変数xが100を超過しなければ処理を繰り返す」といった書き方をする言語は、手続き型言語の典型例である。
手続き型言語は、初心者でも書きやすいが、関数型言語と異なり、数学的に処理を書くことが難しい。関数型言語においては、関数の呼び出しを使って数学的にプログラムを記述する「関数型プログラミング」を行う。多くの場合、変数を「変更」(破壊的作用)することを前提とする手続き型よりも、変数を「その時その時作り出す」関数型のほうが、抽象度が高く、書く際においても読む際においても、関数型プログラミングの考え方を用いることが望ましい。C言語では、そのような関数型プログラミングを行うことは難しく、まるで「手続き型のために頭が退化する」ようにしかプログラムが書けない。
また、C言語は、機械に近い。アセンブラにおいてはメモリアドレスの操作を多用するが、C言語はアセンブラと同じように、ポインタというメモリアドレスの操作による変数の操作を行う。そのため、「C言語はアセンブラの拡張版である」と言われることもある。だが、カーネルやデバイスドライバのような、システムの中でも低水準のレイヤーでは、逆にそのおかげで自由度が高く、ハードウェアを操作しやすい。
それから、C言語は、データ構造を自分で作る場面が多い。Javaのようなクラスライブラリが豊富に用意されている言語と異なり、C言語はデータ構造を使いたい時は多くの場合自分で作る必要がある。だが、C言語を機能的に拡張したC++言語においては、STLのようなデータ構造を使うこともできる。C言語にはリストやハッシュテーブルはない(配列や構造体はある)し、スタックやキューもないが、それらを使いたければ自分で実装するか、C++のSTLを使う。
そして、C言語は、熟練しなければ分からないような、直感に反する言語仕様が多い。数値や構造体が値型で、関数の引数として値渡しされるのに対して、配列や文字列はポインタとして表現され、関数にはポインタとして渡される。それらを簡単に==で比較することもできず、代入やコピーにも特殊なAPIが必要。だが、C言語は比較的小さな言語であり、すべての仕様を知るのは難しいことではない。だが、C言語を拡張したC++言語では、さまざまな機能がこれでもかというぐらいてんこ盛りで追加されており、C言語にあるのとはまた違うやり方で同じような機能(配列やmalloc()に対するSTLのベクターなど)もあり、またWindows APIやMFCのようにプラットフォームによって機種依存の拡張が加えられることも多々あり、C/C++の世界はまるで「カオス」である。
そして、C言語はアセンブリ言語に比べれば移植性は高いが、Javaなどに比べれば移植性は低い。Windowsなどでプラットフォーム依存のAPIが使われることが多く、クロスプラットフォームなアプリケーションを書くためには努力しなければならない。それでも、C言語からCPUアーキテクチャ別の機械語の実行ファイルにクロスコンパイルによって変換でき、機械語の命令そのままであるアセンブリ言語よりは移植性は高いため、UNIXのようにOSのカーネルのCPU移植性を向上させるために低水準レイヤーはC言語で書かれることが多い。
このように、C言語には欠点ばかりあるように見えるが、実行速度は速い。実行される前に機械語の実行ファイルができ、コンパイラによってはさまざまな最適化が行われるため、Pythonで書いたコードをC言語で書き直すだけで速度が何倍も高速化することがある。また、そもそもインタープリタ言語では、カーネルやシステムソフトウェアを書くことはできない。インタープリタ言語で書けるのは、上位層のアプリケーションだけであり、システムの下位レイヤーとしてC言語のようなネイティブな機械語を吐き出す仕組みは、すべてをアセンブラで書かない限り絶対に必要となる。PythonやRubyのようなインタープリタ言語であっても、言語処理系であるインタープリタ自体はC言語で書かれることが多い。
C言語でプログラムを書く際には、実行する前に必ずコンパイルする必要がある。コンパイルして実行ファイルを吐き出すための一連の作業を「ビルド」という。ビルドには専用のビルドツールが使われることもあり、C言語の場合はmakeというUNIXコマンドがよく使われる(GNU版はGNU make)。makeを使うためには、ビルドするための手順を書いたファイルであるMakefileを書かなければいけないため、ひと手間かかってしまう。
このようなC言語と異なり、PerlやPHPやPythonやRubyのようなインタープリタ言語は、プログラムを実行するその時点で、インタープリタと呼ばれる言語処理系が逐次的にプログラムを翻訳しながら実行する。実行中に翻訳するので、速度は遅いが、ビルドのようなひと手間が必要ない。また、C言語で書くのに比べて、機械よりも数学的な数式に近く、C言語よりも高水準なプログラムが書ける。なので、手軽でありながら生産性が高い。また、言語の仕様も、直感に従う自然な文法をしている言語が多く、機能もたくさんあり、データ構造だけではなく多くのモジュールが用意されていて、モジュールを使うだけで簡単にプログラムが書ける。また、インタープリタ言語では動的型付けを採用した言語が多く、煩わしいデータ型をプログラマが考えなくても、言語処理系が勝手にデータ型を考え、自動的に適切に変換してくれるため、初心者には優しい(大規模な開発においては、逆に静的型付け言語のほうが、バグや誤動作を防ぐために、プログラマから好まれる場合もある)。なので、初心者がプログラムの練習をする際には、初心者向けであることに定評のあるPythonのような高水準言語を使うことが推奨される。
また、これ以外に、JavaのようなVM型の言語がある。VM型の言語では、コンパイルした時点ではどのCPUにも依存しないバイトコードのクラスファイルを吐き、そのクラスファイルを環境ごとに用意されているVM(仮想マシン)の上で実行する。VMさえ用意されていれば、どのCPUでも同じように動作するため、移植性が高い。特に、パソコンやサーバーのような便利な環境と、モバイルや組み込みのような制限された環境で、同じプログラムを簡単に実行できるため、組み込みの開発には向いている。また、Javaではオブジェクト指向という大規模な開発に効果的な考え方を採用しており、クラスライブラリでは多数のデータ構造とAPIが提供されているため、大人数のチームで大規模なシステムを協力して開発するのに向いている。Javaと同様のVM型の言語に、Windowsの.NETがあるが、.NETは基本的にWindowsで動作することを前提としており、UNIX環境でも動くものの標準的ではなく、移植性はJavaほどは重視されていない。
JavaがVMで動くということは、大きな優越性がある。そのひとつが、ガーベッジコレクションの存在だ。C/C++においては、malloc()で確保したリソースは必ずプログラムの中でfree()を呼び出して、使い終わってから解放しなければならない。そこをきちんとしなければメモリリーク(システムのメモリをいくらでも無駄に使って、最終的には使い果たすバグ)が起きる。Javaでは、ガーベッジコレクションがあるおかげで、そのようなメモリリークが起きない。使い終わった後で解放しなくても、参照のなくなったメモリ領域を自動的に解放してくれる。
これらの言語すべてに優越するものとして、Lispのような関数型言語がある。Lispは関数とマクロを使った関数型言語で、手続き型にはできない「非常に抽象度の高いプログラム」を書くことができる。ただし、Lispは独自の括弧を多用したプログラムコード(S式)を採用しているため、熟練しなければ、プログラムコードの書かれた意味を推測しづらい。それでも、熟練者にとってはそのようなコードこそが正しいコードであり、手続き型とは真逆の高度で賢い数学的な言語の機能を使うことができる。
なので、できるだけLispのような関数型言語を使いたいが、S式は読みづらく分かりづらいと考えている人に、ScalaというAltJava言語が存在する。AltJavaとはJava言語を置き換える言語のことで、JavaのVMで動きながら、言語の文法と標準ライブラリだけをScalaなどの別の言語に置き換えたもの。ScalaはJavaとLispを融和して進歩させたような言語であり、Javaにはないトレイトやミックスインのような新しく進歩したオブジェクト指向の機能を持ちながら、Lispのように言語仕様そのものを書き換えてしまうような機能(制御構文すら自分で拡張できる)という特徴がある。まさしく、Scalaを使うべきである。
Scalaよりも新しい言語は確かにある。そのひとつがRustだ。Rustは、Javaのようなガーベッジコレクションではない方式でメモリセーフを実現し、C++の代替になる言語として流行っている。確かに、RustにC++を置き換えてほしいと思っている人はたくさんいて、LinuxカーネルやWindowsの開発者もRustを取り入れようとしている。だが、Rustの考え方はあまりに独自すぎて、逆に言えば進歩する意味のないところが異常に進歩している。その一例が所有権やライフタイムの概念であり、きちんと理解できる人はそもそも少なく、従来のC/C++と同じ感覚で使うと必ず面倒くさいことが起きるなどの問題がある。Rust以外にもGoのような新しい言語はあるが、GoはScalaとよく似た言語であり、競合関係にあるものの仲は悪くない。
2025.06.04