プログラミング言語概論
プログラムを使う前に、ちょっとだけプログラミング言語について。
プログラミング言語は、おおまかに手続き型、関数型、スタック型、論理型の4種類に分けられます。
ここでオブジェクト指向が入っていないことに「アレ?」と思われるかもしれません。実のところ手続き型と関数型との合わさった言語があるように、あるいはそれとは別の話としてオブジェクト指向は計算の方法とは別のものであって、どの計算のやりかたとも基本的には合わせられます。
というのもオブジェクト指向のオブジェクトというのは、よくある言語でのrecordにすぎないからです。recordは、もしかしたらrecordかもしれませんし、リストかもしれませんし、配列かもしれません。
で、結局はrecordというデータと、それに対しての処理を一纏めに書く、それだけがオブジェクト指向です。
あるいはスクリプト言語が入っていないのを「アレ?」と思われるかもしれません。ですが、スクリプト言語というくくりは、実行のされ方とか利用のされ方からの分類なので、これとは分類が違うわけです。
大昔は、大した計算ができない言語くらいのとらえ方でよかったんですが、大昔のある時点で、そうでもなくなっています。
これについて先に簡単に片付けます。
シェルの言語、要はシェル・スクリプトが書かれている言語ですが、これはまぁわかりやすいです。
さて、sedという、えーと、入力を書き換えるエディタというか、スクリプト言語というかがあります。どんなものかをwikipediaから少し:
y/検索文字/置換文字/
とか
s/検索パターン/置換文字列/g
とか。あとラベルを使ってのジャンプもあったりします。
で、困ったことに、こんな記法なのにチューリング完全、つまりは簡単に言えば何でも計算できることがわかっています。他の言語で書く方が簡単なので、普通はそんな使い方はしませんが。
ただし、ここで「置換」とか書いていますが、これは要は入力を書き換えて出力を得るわけです。この「書き換え」というのは結構大事な考え方ですので、ここでちょっと触れておくことにしました。
ちょっとだけ、これについて書いてみます。一桁の数字同士の足し算をするとします。入力の記法としては、普通に "1+2" とか。ここで、"1" を "x" に、"2" を "xx" に置き換えてやるとします。そして最後に "+" を消してやります。すると "xxx" というのが残ります。はい。"x" の個数として、"1+2" の計算ができました。実際にいろいろやろうとするともっと面倒になりますが、こういうやりかたでも計算できるという例として見てください。
さて、よくあるのは、あるいはおそらくよく目にするだろうものは手続き型の言語だろうと思います。足し算をして、次にかけ算をしてというように書くのは、みんな手続き型と思ってもらっていいだろうと思います。
それでですが、よく目にす分、言語も多いわけです。それぞれに特徴があり、それぞれの特徴を売りにして、多くのユーザを獲得しようとしています。
たぶん、わかり易いといえばわかり易いのかな。ただ、これだけに慣れてしまうと、他のものを理解しにくくなってしまいます。どれかに慣れたら、他の型の言語にも慣れることをお勧めします。
次に関数型ですが。これは、まぁオリジンとしてはlispです。lispインタプリタが作られたのには面白い逸話があって、"eval" という関数を作っちゃったら、「あれ? これインタプリタになるんじゃね?」と気付き、それでインタプリタになっちゃったという話があります。
lispや他の関数型の言語が元にしているのはλ計算というものです。これも少しwikipediaから:
(λx. x + 2) 3
というのがあったとします。ここでは、"x+2" が関数の本体です。"λx"で、「λ式ですよ」ということと「変数はxを使います」ということを宣言しています。で、ここでαとかβとかηとかありますが、そこは無視して、上の式を見ると、xに3を入れてやります。すると関数の本体は "3 + 2" となります。最後にそれをまた "+" というものについて計算してやると "5" という結果が得られます。
上でsedについて書き換えということを書きましたが、λ計算もλ式の書き換えで計算をします。上で書き換えというのは重要と書いたのは、そういう理由もあってです。
ここで更に重要なのは、この例では "x" を "3" に書き換えている程度ですが、"x" を別のλ式に書き換えても何の問題もないということです。"3" ではなく、かけ算のλ式にでもしてやると、「かけ算のあと足し算をする」とかなんとかになるわけです。かけ算をしてから、その結果を "3" のところに置くと考えてもいいですけど。
で、関数型としてlispを挙げるのには抵抗がある人もいるかもしれません。まぁ実際、実用性とかわかりやすさとかから、lispは関数型と手続き型のハイブリッドとも言えます。あからさまに "prog" 関数とかもあったりしますし。
それで、lispの特徴みたいなものとしてはおおまかに言うなら、括弧の内側から外側に向かって計算すると読んでもらうと言えるかもしれません:
(* a (+ b c))
こんな感じかな。まぁ実際にはそうでない場合も多々ありますが。
あとは、最近は手続き型の言語にも導入されていますが、無名関数が挙げられます。これは慣例として "lambda" (つまりλ) という標識で示されることが多いかと思います。関数としては定義しないけど、関数扱いというか。実際には、というか歴史としては、lambdaの標識が着いたものに名前を着けることで、関数としたとかなんとかありますけど。
Python (3かな) では、こんなとか
squares = list(map(lambda x: x**2, range(10)))
これは "range" とか "map" とかっていう要素もありますけど。この場合、0から9までの整数を二乗したもののリストを得る感じです。二乗してやる関数そのものは、名前を着けて定義してやらず、ここだけでこう書いてやるというものです。
"lambda" の中に "range" が入っていて、その外に "map" があるというのは、考えてみるとわかりにくいかもしれないですね。"map" の引数として、"lambda" と "range" あるいはリストがあるという書き方の方がわかりやすいと思いますが。lispなんかだとそういう感じですが。
"range" はともかく、"map" というのも関数型だとよく見る関数であるとか機能です。引数であるリストの各要素を順番に呼び出して、それに対しての計算をしてやるというものです。これ、便利。
"map" に触れたので、ループとかにも少し触れます。
手続き型だと "for" やら "while" やらのループを使うのが基本かと思います。再帰ももちろん使いますが。
対して関数型だと再帰を使う方が基本になります。ループの関数もありますが。
それで、lispは括弧を多用します。ある関数の範囲を示すのに括弧を使っているからです。これは面倒でもありますし、少しは便利なこともあります。便利なのは、ある関数の計算の対象がどこまでなのかが明示されているということです。なので、こんな書き方もできます:
(add 1 2 3 4 5 6 7 ...)
これで、括弧内の総和を得たりということが書けたりします。
ただまぁ、括弧が多くなることは避けられないので、スーパー括弧とかもあったりします。データでも関数でも構わないのですが、その終りのあたりは閉じ括弧がいくつも並ぶなんてことがあります。それを、たとえば "]" 一文字で全部閉じてしまうとか、"[" でその始まりを示しておいて、閉じる方は "]" 一文字で、そこまでの括弧を全て閉じるとかです。
次にスタック型ですが、これはまぁ手続き型とも言えます。ただ、データをスタックに積んでおいてから、手続きあるいは関数を書いてやることで、スタック上のデータに対して計算をするようなものです。
こんな感じです:
1 2 + .
これで、1をスタックに積んで、その上に2を積んで、"+" でスタックの上2つの足し算をし、かつその結果をスタックに積み直し、最後に "." でそのスタックの内容を表示するというような感じです。
lispの例だと、引数はいくつあってもかまわない場合がありましたが、スタック型では、"+" とかが幾つのスタックを見るかは、それぞれにおいて決っています。
で、スタック型の何が嬉しいかというと、引数の引き渡しについての処理がどうでもよくなる点です。基本的に計算の対象となるデータは全てスタックに積まれています。"+" を呼び出したからと言って、"+" に与える引数がどうなるというようなことは気にしないわけです。手続き型でも関数型でも引数とかをどうやって引き渡してやるかというのは面倒な話です。スタック型は、そこを気にしなくていいわけです。
もっとも、それは言語の処理系の話であって、スタック型の場合、人間が気にしてやらないといけないわけです。スタックに積まれているものを消費しすぎたとか、消えていないといけないものが残ったとかだと、バグになります。
最近、個人的にお気に入りの "REBOL" という言語は、関数型とスタック型っぽいのが混ざっています。基本は関数型っぽいのですが、関数が取る引数の数は決っています。そのため、色々な括弧を書かなくて済みます。もちろん、それは、「ある関数の範囲がどこまでなのかわかりにくい」という面もありますが。
関数型とスタック型のと言っていいかと思いますが、その特徴として、"if" とか "for" とかにあたるような制御構造も定義できるというところがあります。
lispの入門書には、たぶんそういう例題があると思います。またスタック型のforthだと、生のforthでオブジェクト指向の機能を実装するなんてことがあったりします。
これは、主眼としての機能と言っていいのかは悩むところです。結果としてそうなっているという面もあるからです。
でも、そういう柔軟さは好きなんですけどね。
論理型ですが、prologだと、ファクトとルールを書いておいて、ある述語が真になるものを出力するような感じです。論理型は苦手なので、wikipediaとかを参照してください。
bird("カラス").
fly(X) :- bird(X).
?- fly(X)
カラス
みたいな感じかな。ここでは "X" が "bird" を真にするなら、その "X" は "fly" を真にするというような感じで、カラスが出てきてます。
まぁ、最近はこういうののハイブリッドがまた進んでたりするので、ちょっと面倒だったりするかもしれませんが。
ですが、こういうの結構面白いと思います。




