前説

プログラミング言語 LISP について名前を知っているという人は多いと思います。 また「括弧がいっぱいだが文法は単純」だとか「関数型言語の元祖っぽい」だとかいったことも良く知らているでしょう。 しかし、逆に言えばその程度しか知らないという人が (プログラミングの素養がある人であっても) 圧倒的多数なのではないでしょうか。 この記事はそういった層に向けて LISP の基本的な部分を解説しようとする試みのひとつです。 特に「文法が単純」という部分に着目して解説します。

LISP はジョン・マッカーシ氏が提案したプログラミング言語です。 また、そこから派生した多くの言語群のこともまた LISP と呼ばれています。 つまり、単に LISP と言ったときには何らかの LISP の仕様のことを指しているのではなく、無数の言語を一括りにした呼び名であるということです。 とは言え、現代でも使われている LISP 系の言語は

  • Common Lisp
  • Scheme
  • Clojure
  • Emacs Lisp

くらいのもので、他はかなり細々としたものだと思います。

私自身は Scheme に傾倒しているので具体的な仕様としては Scheme を中心に解説しますが、考え方は他の LISP 系言語にもおおよそ当て嵌るはずです。 ただ、膨大にある LISP 系言語には例外的な特徴を持つものもあるので全てに当て嵌るわけではないことは断っておきます。

関数

LISP を構成する最も重要で基本的な要素は関数です。 (Scheme では手続きという用語を用いていますが、この記事では LISP の伝統に従い関数という用語で一貫させることにします。) 何らかの入力に対して結果を返す仕組みです。

簡単な例として足し算をする式を書いてみます。

(+ 1 2)

これは一般的な中間記法で言えば 1+2 と同じ意味です。 括弧で表現されているのはリストで、先頭要素の関数に後続の引数を与えて呼出されるという仕組みです。

構文

一方で、入力に対して結果を返すだけでプログラムを構成することは難しいことは明らかです。 例えば変数を定義する必要があるときはこのように書きます。

(define x 1)

これで x という変数を定義すると同時に数値の 1 と結びつけることが出来ました。 ですが、 define は関数ではありません。 定義する前には x という変数は作られていないのですから、それを define という関数に渡すというのは辻褄が合いません。

では define が何なのかというと構文です。

LISP では関数の呼出しも構文も共に括弧で表現され、それを区別するものは名前だけです。 どういう名前が構文でどれが関数なのかというのを見分ける一般的な規則というものもありません。 自由に名付けることが出来ますし、処理系が提供するそれらもある程度の作法はあるにせよ割といいかげんな名前が付いています。

私自身は自然に受け入れてしまったのですが、関数呼び出しと構文が見た目で区別が付かないまま混在するというのは最初は混乱する人は多いようです。

複雑な構文

構文も関数と同様に括弧で囲まれているだけというわけではなく、もっと複雑な形式を持っている場合があります。 例として、一時的な変数を導入する let 構文の用例を示します。

(let ((a 1)
      (b 2))
  (+ a b))

これは a1 を結びつけ、 b2 を結びつけている状態で ab を足すという意味です。

しかしながら、 (a 1) という部分を抜き出しても a1 を結び付けるという意味はありません。 ((a 1)(b 2)) もまた、これだけでは意味を成しません。 この括弧はあくまで let 構文の一部なのであって、単独で抜き出すことは出来ないのです。 構文はその内側の括弧の使い方の規則を決める場合があります。 それはそれぞれの構文によって違うので覚えておかなくては正しく使えません。

つまり、 LISP のプログラムは括弧で表現されるリストであり、そのリストを構成させるための字句規則 (S式) は単純な規則を持っていますが、リストの上で表される構文はそれぞれ独自の規則を持っているというわけです。 文法の複雑さが字句ではなくリストの上に移動しただけで、全体としての複雑さはすごく単純というわけでもないのです。 (とは言え、現代的なプログラミング言語の中では単純な方だとは思いますが。)

このことは XML を知っている人に対しては well-formed と valid の違いと考えれば理解しやすいと思います。 リストとして正しく読み取れるかという規則と、読み取ったリストが LISP プログラムとして正しいかは別物なのです。

構文を作る

名前で区別がつかないとは言っても用意されている (有限の) 構文を覚えてしまえば良いのだろうと考えた人もいるかもしれません。 しかし、構文を作ることも出来ます。 それが悪名高い (しかし LISP の力強さを支える重要な要素である) マクロです。

新しく定義した構文がどんな構造を持っているのかは、もちろんその定義によります。 新しい構文が増えるのです。 場合によってはひどく混乱したプログラムになってしまうこともあるかもしれません。

しかし、 LISP プログラムの中でまるっきり別のパラダイムを持ち込んでプログラミングしたいといったことさえ可能にします。

今では各分野に適したプログラミング言語があるので LISP の上に全く別のパダダイムを作り上げる必要がある場面はもうあまりないとは思いますが、様々な分野をまたいだ横断的なプログラムを書く場合や、まだその問題領域を解決するのに適切なパラダイムが確立していないといったときには新しい構文を作る能力が活躍します。

とはいえ、可能であればマクロを使うのは避けるべきだと考えられています。

まとめ

LISP の字句的な文法は他のプログラミング言語と比べれば単純ですが、文法全体で見ればそうでもないです。