第1章 やさしい入門nobulign.way-nifty.com/lisp/krel.pdf第1章 やさしい入門...

43
第1章 やさしい入門 まずは、Emacs-Lisp を手短に紹介することから始める。われわれの目的は、細かい点や、公 式の規則や、例外に深入りせず、現実のプログラムにかかわる Emacs-Lisp の本質を示すことで ある。ここでは、完全あるいは正確であることは二の次とする(あげた例は正しいつもりだ が)。読者には、できるだけ早く役に立つプログラムが書けるようになってもらいたい。それ には、変数と定数、算術、制御の流れ、関数、基本的な入出力といった基本事項に注意を集中 する必要がある。より大きなプログラムを書くために非常に重要な Emacs-Lisp の特徴は、ここ では省くことにする。そういったものは、シンボル、構造体、豊富にある関数の大部分、いく つかの制御の流れの文、それに数多くのこまごました機能などである。 もちろん、このやり方には欠点もある。最も大きなものは、特定言語の特徴について、まと まった話が一箇所にまとまらないことと、説明が舌足らずで誤解を招きやすいことである。そ れから、例だけでは Emacs-Lisp の全機能を使えないから、例は意図したほど簡潔にもエレガン トにもなりえない恐れもある。われわれとしてはそうならないように努めたが、注意はしてい ただきたい。もう一つの欠点は、後の章で本章の一部を繰り返す必要性があるということであ る。ただし、この繰返しは、読者にとってわずらわしいものというよりは、一層役に立つもの であることを望みたい。 いずれにせよ、経験豊かなプログラマは、本章の材料から自分のプログラミングに必要なも のをすぐに応用できるはずである。初心者は自分で規模の小さな類似のプログラムを書いてみ るとよいであろう。どちらの方も、第 2 章から始まるもっと詳しい説明を学ぶための概論とし て本章を役立てていただきたい。 1.1 手始めに 新しいプログラミング言語を学ぶ唯一の方法は、それでプログラムを書いてみることである 。 最初に書くべきプログラムは、どんな言語でも同じで、例えば次のようなものである。 hello, world という単語を印字せよ。 これが基礎となる障害物だとしよう。それを飛び越えるためには、どこかでプログラム・テ キストをつくり、それを(上手にコンパイルし、)ロードし、ランし、出力がどこに行くかを 見いださなければならない。こういった機械的な細かなことをマスターすれば、他のことはす べて比較的容易である。 Emacs-Lisp で、hello, world と印字するためのプログラムは、次のようになる。 (require 'stdio) (defun main () (printf "hello, world\n")) プログラムをどのようにして実行させるかは、どのシステムでも同じである。このプログラ

Upload: others

Post on 24-Mar-2020

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

第1章 やさしい入門

 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

式の規則や、例外に深入りせず、現実のプログラムにかかわるEmacs-Lispの本質を示すことで

ある。ここでは、完全あるいは正確であることは二の次とする(あげた例は正しいつもりだ

が)。読者には、できるだけ早く役に立つプログラムが書けるようになってもらいたい。それ

には、変数と定数、算術、制御の流れ、関数、基本的な入出力といった基本事項に注意を集中

する必要がある。より大きなプログラムを書くために非常に重要なEmacs-Lispの特徴は、ここ

では省くことにする。そういったものは、シンボル、構造体、豊富にある関数の大部分、いく

つかの制御の流れの文、それに数多くのこまごました機能などである。

 もちろん、このやり方には欠点もある。最も大きなものは、特定言語の特徴について、まと

まった話が一箇所にまとまらないことと、説明が舌足らずで誤解を招きやすいことである。そ

れから、例だけでは Emacs-Lispの全機能を使えないから、例は意図したほど簡潔にもエレガン

トにもなりえない恐れもある。われわれとしてはそうならないように努めたが、注意はしてい

ただきたい。もう一つの欠点は、後の章で本章の一部を繰り返す必要性があるということであ

る。ただし、この繰返しは、読者にとってわずらわしいものというよりは、一層役に立つもの

であることを望みたい。

 いずれにせよ、経験豊かなプログラマは、本章の材料から自分のプログラミングに必要なも

のをすぐに応用できるはずである。初心者は自分で規模の小さな類似のプログラムを書いてみ

るとよいであろう。どちらの方も、第 2章から始まるもっと詳しい説明を学ぶための概論とし

て本章を役立てていただきたい。

1.1 手始めに 新しいプログラミング言語を学ぶ唯一の方法は、それでプログラムを書いてみることである。

最初に書くべきプログラムは、どんな言語でも同じで、例えば次のようなものである。

hello, world

という単語を印字せよ。

 これが基礎となる障害物だとしよう。それを飛び越えるためには、どこかでプログラム・テ

キストをつくり、それを(上手にコンパイルし、)ロードし、ランし、出力がどこに行くかを

見いださなければならない。こういった機械的な細かなことをマスターすれば、他のことはす

べて比較的容易である。

 Emacs-Lispで、hello, worldと印字するためのプログラムは、次のようになる。

(require 'stdio)

(defun main ()

(printf "hello, world\n"))

 プログラムをどのようにして実行させるかは、どのシステムでも同じである。このプログラ

Page 2: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

ムをEmacsの*scratch*バッファで走らせれば

 hello, world

が出力として出てくるのである。

 次に、このプログラム自体について少し説明しよう。一つの Emacs-Lispプログラムは、その

大きさはどうであれ、関数と変数などからなる。関数には、行なうべき計算の過程を指示する

文、および計算で使われる値を格納する変数が含まれる。Emacs-Lispの関数は Fortranのサブ

ルーチンや関数、あるいは Pascalの手続きや関数と似ている。上の例では、mainがそうした

関数になっている。普通は、関数には自分の好きな名前を付けて良い。mainは特別な名前では

なく、プログラムのどこかにmainがなければならないというわけではない。

 プログラムでは普通その仕事を行なうのに他の関数を呼び出すが、それは自分の書いたもの

であることもあれば、手元にあるライブラリの関数であることもある。

 このプログラムの最初の行

(require 'stdio)

は、標準入出力ライブラリについての情報を含めるべきことを、Emacsに対し指示するもので

ある。この標準ライブラリについては、第 7章および付録Bで説明する。

(require 'stdio) ;; 標準ライブラリについての情報を取り込む。

(defun main () ;; 引数のないmainという名の関数

(printf "hello, world\n")) ;; この文字列を印字するのにライブラリ関数

;; printfを mainで呼ぶ。\nは改行記号。

最初のEmacs-Lispプログラム

 関数との間でデータをやりとりする唯一の方法は、呼び出しを行なう関数から相手の関数に

対して、引数と呼ばれる値のリストを渡すやり方である。関数名の次にあるカッコはこの引数

のリストを囲むのに使われる。この例では mainは引数のない関数であることが()で示されて

いる。

 この関数mainは次の文一つだけを含んでいる。

 (printf "hello, world\n")

関数は名前で呼ぶが、その後に引数を並べたものを付ける。ここでは"Hello, World\n"という

引数を付けて、printfという名の関数を呼んでいる。printfは出力(この場合は引用符で囲ま

れた文字列)を印字する(標準出力バッファの終わりに書き込む自作の^^;)ライブラリ関数で

Page 3: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

ある。

 二重引用符"……"で囲まれる任意個の文字の列を、文字列と呼ぶ。さしあたり、われわれに

とっての文字列の唯一の使い方は、printfおよび他の関数の引数としてということになる。

 文字列の中の\nという記号は、改行(newline)という文字の記号で、印字の場合、端末機

を次の行の左端に進めることを示す。この\nを省略すると(これはやってみるべき実験であ

る)、出力の後に改行が行なわれないことが分かるであろう。また、次のように書いても結果

は同じである。

 (printf "hello, world

 ")

 改行はprintfでは決して自動的には行なわれないから、1行の出力は何回かの呼出しで段階

的に行なっても良い。最初のプログラムは次のように書いても、出力は同じである。

(defun main ()

(printf "hello, ")

(printf "world")

(printf "\n"))

 ここで注意したいのは、\nが一つの文字を表わしているということである。\nのようなエス

ケープ符号列は、表現しにくい文字、すなわち目に見えない文字を表わす一般的で、拡張可能

な機構になっている。Emacs-Lisp で使う他の記号には、tabを表わす\t、バックスペースの

\b、二重引用符を表わす\"、バックスラッシュ自体を表わす\\がある。完全なリストは 2.3節

に示す。

Page 4: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-1 手元のシステムでこのプログラムをランさせよ。プログラムの一部を省いて、どん

なエラー・メッセージが出るか、試みてみよ。

演習 1-2 xが上にあげなかったある文字であるとして、printfの引数に\xを含めるとどうな

るかを調べる実験を行なえ。

Page 5: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

1.2 変数と算術 次のプログラムは、C=(5/9)(F-32)という公式を使って、華氏の温度と摂氏の温度の次のよう

な対応表を印字するプログラムである。

 0 -17

 20 -6

 40 4

 60 15

 80 26

 100 37

 120 48

 140 60

 160 71

 180 82

 200 93

 220 104

 240 115

 260 126

 280 137

 300 148

 このプログラム自体は、やはり mainという名の一つの関数の定義からなる。これは hello,

worldを印字するプログラムよりは長いが、複雑なものではない。この例では、コメント、宣

言、変数、算術式、ループ、書式付き出力といった、いくつかの新しいアイデアが使われてい

る。

(require 'stdio)

;; f=0,20,…,300に対して、摂氏-華氏対応表

;; を印字する

(defun main ()

(let (fahr celsius lower upper step)

(setq lower 0) ; 温度表の下限

(setq upper 300) ; 上限

(setq step 20) ; きざみ

(setq fahr lower)

(while (<= fahr upper)

(setq celsius (/ (* 5 (- fahr 32)) 9))

(printf "%d\t%d\n" fahr celsius)

(setq fahr (+ fahr step)))))

Page 6: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

最初の1行

;; f=0,20,…,300に対して、摂氏-華氏対応表

;; を印字する

はコメントであり、この場合、このプログラムでやることを簡潔に説明するものとなってい

る。;(セミコロン)の後ろにあるこうした文章はインタプリタにより無視される。したがって、

コメントはプログラムを理解しやすくするのに自由に使うことができる。入れる場合は、空白

(blank)あるいは改行が書けるところならばどこでもよい。

 Emacs-Lispでは、すべての変数は、一般的に使う前に、普通は関数の始めの実行可能文の前

のところで宣言しておかなければならないかというと、そうではない。let の最初のリスト

(fahr celsius lower upper step)は letフォームの中でローカルに使用したい変数名の羅列で

ある。ここでの変数はすべて数値を扱っており、取り得る値は-8388608〜8388607(24ビット

実装)である。その他には、こういった基本型の配列、構造体、それらのシンボル、それらを

返す関数がある。これらについては後で説明する。

 さて、前の温度換算プログラムの実際の計算は、代入文

(setq lower 0)

(setq upper 300)

(setq step 20)

(setq fahr lower)

で始まり、これで変数には初期値がセットされる。

 表の各行は同じ式で計算されるから、出力行ごとに 1回繰り返すループを使うのが良い。こ

れが次のwhile文の目的である。

(while (<= fahr upper)

)

この while ループは次のように動く。まずカッコ内の条件をテストする。これが真(fahr が

lower以下)であれば、ループの本体(whileの次の 3行)が実行される。次に条件が再チェッ

クされ、真であれば、ループの本体が再び実行される。このテスト結果が偽(fahrが upperを

超える)となると、ループは終りとなり、実行はこのループの後に続く文に移る。このプログ

ラムではそこから先には文がないから、そこで終りである。

 whileの本体は、温度換算プログラムでみたように 2番目以降のリストである。whileで制御

される文は tabストップで字下げして、どの文がループの内部にあるかが一目でわかるように

する。字下げは、プログラムの論理構造を強調するのによい。Emacs-Lispでは文の位置はどこ

にあろうとまったくかまわないが、適当な字下げと空白のスペースを利用することは、プログ

ラムを見やすくするのに不可欠である。われわれがすすめたいのは、リストの記述をどのよう

に区切ってもよいので、必ずインデントコマンドを使用して字下げを行なうことだ。

Page 7: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

 さて、先の例では、仕事の大部分はループの本体で行なわれる。摂氏の温度を求めるのは次

の式である。

 (setq celsius (/ (* 5 (- fahr 32)) 9))

ここで、単に(/ 5 9)を掛ける代わりに、5を掛けてから 9で割っている理由は、Emacs-Lispで

は他の多くの言語と同様、整数の割算では切捨てが行なわれて、小数部が切り捨てられてしま

うからである。5と 9は整数だから、(/ 5 9)は切り捨てられてゼロになり、もちろんすべての

摂氏温度がゼロになってしまう。

 この例は、printfがどう働くかをもう少しよく示す例にもなっている。printfは、実際には

汎用の書式変換関数である。これについては第 7章で詳しく説明する。ここでは、最初の引数

は印字すべき文字列で、%記号はそれぞれの他の(第 2、第 3、……の)引数の一つをどこに代

入すべきか、それをどんな形で印字すべきかを示している。例えば、%dは整数の引数を表わす

から

 (printf "%d\t%d\n" fahr celsius)

という文は、二つの整数fahrと celsiusの値を、間にタブ(\t)をはさんだ形で印字せよとい

う指示になる。

 printfの最初の引数では、おのおのの%記号は、それに対応する第2、第 3などの引数と対に

なっている。これらは数と型が正しくあっていなければならない。さもないとエラーになって

しまう。

 ところで、printfは Emacs-Lispの一部ではない。Emacs-Lispに printfなどという関数は定

義されていないからである。printfは単に、C言語で通常使える標準のライブラリ・ルーチン

を模したものにすぎない。

 ここではEmacs-Lisp自体に重点を置くために、入出力については第 7章まであまり述べない

ことにする。

 さて、この温度変換プログラムにはいくつかの問題点がある。まず簡単なほうからいうと、

数が右揃えになっていないために、出力が非常にきれいだとはいえないことである。しかし、

これを直すのは何でもない。printf文の各%dに幅を付加すればよく、それで印字される数はす

のフィールド内で右詰めになる。例えば

 (printf "%3d\t%6d\n" fahr celsius)

として、各行の最初の数字を 3桁の幅のフィールドに、2番目の数字を 6桁幅のフィールドに印

字することにすれば、結果は次のようになる。

  0 -17

  20 -6

  40 4

  60 15

Page 8: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

  80 26

 100 37

 …

 さらに重要な問題は、整数で計算を行なっているために、摂氏の温度があまり正確でないこ

とである。例えば、0Fは実際には-17.8Cであって-17Cではない。より正確な答えを得るには、

整数の代わりに、浮動小数点数演算を使うべきである。これを直すのも何でもない。次に示す

のは第2版である。

(require 'stdio)

;; f=0,20,…,300に対して、摂氏-華氏対応表

;; を印字する; 浮動小数点数版

(defun main ()

(let (fahr celsius lower upper step)

(setq lower 0) ; 温度表の下限

(setq upper 300) ; 上限

(setq step 20) ; きざみ

(setq fahr lower)

(while (<= fahr upper)

(setq celsius (* (/ 5.0 9.0) (- fahr 32.0)))

(printf "%3.0f\t%6.1f\n" fahr celsius)

(setq fahr (+ fahr step)))))

 これは前のプログラムとほぼ同じだが、変換の公式をより自然な形に直してある。前の版で

は、整数による演算の結果が切り捨てられてゼロになるために、(/ 5 9)が使えなかった。しか

しながら、定数の中の小数点は浮動小数点を表わすから、(/ 5.0 9.0)は切り捨てられない。こ

れは二つの浮動小数点数の比だからである。

 算術演算では、被演算数が整数なら、整数演算が行なわれる。しかし、引数の片方が浮動小

数点数、他方が整数のときには、浮動小数点演算を行ない、結果を浮動小数点数値オブジェク

トに格納する。上のプログラムのように、整数値をもつことがわかっている場合でも、浮動小

数点定数に明示的に小数点を付けて書くのは、それが浮動小数点数であることを人間の読み手

に強調するためである。

 次にprintfの変換指示子%3.0fは、浮動小数点数を少なくとも 6文字幅に、しかも小数点の

後は1桁にして印字するための指示である。これで出力は次のようになる。

  0 -17.8

  20 -6.7

  40 4.4

 …

Page 9: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

変換仕様からは幅と精度は省いてもよい。%6f は数が少なくとも 6文字幅であることを表わ

す。%.2fは小数点の後が2文字だということを指示するだけで、幅には制限を付けない。%fは

数を浮動小数点数として印字せよというだけである。

 %d 10進数として印字

 %6d 10進数として印字、少なくとも 6文字幅に

 %f 浮動小数点数として印字

 %6f 浮動小数点数として印字、少なくとも 6文字幅に

 %.2f 浮動小数点数として印字、小数点の後は2桁

 %6.2f 浮動小数点数として印字、少なくとも 6文字幅で、小数点の後は2桁

さらに、printfでは、%oは 8進、%xは16進、%cは文字、%sは文字列、%%は%自体という区別

が行なわれる。

Page 10: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-3 表の上に見出しを印字するように温度換算プログラムを変更せよ。

Page 11: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-4 温度を摂氏から華氏に換算するプログラムを書け。

1.3 Do文 周知のように、プログラムを書くにはいろいろのやり方がある。温度換算プログラムを別の

形に書いてみよう。

(require 'cl)

(require 'stdio)

Page 12: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

;; 摂氏-華氏対応表を印字する

(defun main ()

(do ((fahr 0 (+ fahr 20)))

((> fahr 300))

(printf "%3.0f\t%6.1f\n" fahr (* (/ 5.0 9.0) (- fahr 32.0)))))

これでも同じ答が出るが、プログラムは違って見える。大きく改めた点は、変数の多くを除い

たことで、fahrのみを残している。上限・下限ときざみの大きさは、do文の定数としてのみ書

いた。このdoはそれ自体が新しい構文である。また摂氏の温度を求める式は、別個の代入文に

する代わりに、printfの 3番目の引数にしてある。

 この最後の変更は、Emacs-Lispにおけるごく一般的な規則、すなわちある型の変数の値を使

うことが許される場合には、それがどこであれ、同じ型の式を使ってもよい、という場合の一

例である。printfの第 3引数は、%6.1fに合う浮動小数点数値でなければならないから、任意

の浮動少数式がそこに書けるわけである。

 さて、do自体はループであり、whileの一般化になっている。これを前に出てきた whileと

比較すると、その役割は明白であろう。doには三つの部分がある。最初の

 (fahr 0 (+ fahr 20))

は、ループの本体に入る前に一度だけ実行される。第二の

 (> fahr 300)

という部分は、ループを制御するテストすなわち条件である。この条件を評価して、これが偽

であれば、ループの本体(ここでは単にprintf)が実行される。次に、再初期化のステップ

 (fahr 0 (+ fahr 20))

が実行され、先の条件が再び評価される。このループは条件が真となれば、終りとなる。while

と同様に、このループの本体は、一つの文であってもよいし、一群の文であってもよい。また

初期化と再初期化の部分は、一つの式なら何でも許される。

 whileと doはどちらを選んでもよく、わかりやすいほうを使えばよい。通例、doのほうが

whileよりもコンパクトで、ループを制御する文が一ヶ所にまとめて置けるので、初期化と再

初期化が一つの式であって、論理的に関係があるループには、doのほうが適している。

Page 13: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-5 温度換算プログラムに手を加えて、表を逆順に、すなわち 300度から 0度へという順

に印字するように直せ。

1.4 記号定数 温度換算の話を終える前に、一つ考えたいことがある。プログラムの中に 300とか20という

"魔法の数(マジックナンバー)"を埋め込むのは悪い習慣である。これは、プログラムを後で

読まなければならない人には、たいした意味をもたないし、それを系統的に変えるのも困難で

ある。幸いにして、Emacs-Lispにはそうした邪魔な数を避ける方法がある。プログラムの初め

に置くdefconst構文がそれで、これを使えば、次のように記号名すなわち記号定数として、特

定の文字列を定義することができる。

 (defconst 名前 それと置き換えるべきテキスト)

 その後、インタプリタでは、そうした名前が引用符なしに出てくると、それを対応する文字

列に置き換えるのである。置き換えられる文字列の方は、任意の文字列でよく、数値に限られ

Page 14: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

ない。

(require 'cl)

(require 'stdio)

(defconst LOWER 0) ; 表の下限

(defconst UPPER 300) ; 上限

(defconst STEP 20) ; ステップ・サイズ

;; 摂氏-華氏対応表を印字する

(defun main ()

(do ((fahr LOWER (+ fahr STEP)))

((> fahr UPPER))

(printf "%3.0f\t%6.1f\n" fahr (* (/ 5.0 9.0) (- fahr 32.0)))))

ここでLOWER、UPPER、STEPという量は定数であって、変数ではないから、宣言しなくてよい。

記号名は、小文字の変数名と容易に区別できるように、大文字で書くのが普通である(?)。

1.5 文字入出力 次に、文字データに対し簡単な操作を行なう相互につながりのある一群のプログラムを考え

る。すぐわかるように、プログラムの多くは、ここで論ずるプロトタイプ(試作品)の拡張版

にすぎない。

 標準入出力ライブラリによってサポートされる入出力のモデルは非常に簡単である。テキス

トの入力あるいは出力は、それがどこで発生したか、あるいはどこへ出力するかにはよらずに、

文字のストリーム(流れ)として扱われる。このテキスト・ストリームは、行に分割された文

字列である。各行はゼロあるいはそれ以上の個数の文字の後に改行記号の付いた形をとる。各

入力あるいは出力のストリームをこのモデルに合うようにするのは、ライブラリの責任である。

ライブラリを使うEmacs-Lispプログラマは、プログラムの外で行がどう表現されているかにつ

いては心配する必要はない。

 標準のライブラリには、一度に 1文字を読み書きする関数が用意されている。getcharは、

呼ばれるごとにテキスト・ストリームから次の入力文字をもってきて、その値として、その文

字を返す。すなわち

 (setq c (getchar))

の後では、変数 cには入力から次の文字が入る。文字は普通はキーボードから打ち込まれるも

のである。ファイルからの入力については第 7章で述べる。

 関数putcharは呼ばれるごとに文字を出力するためのものである。

 (putchar c)

Page 15: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

は、通常、画面に整数変数 cの内容を示すのに使われる。putcharと printfの呼出しは、交互

に行なってもよい。出力は呼出しの行なわれた順に行なわれる。

1.5.1 ファイルの複写 getchar と putcharがあれば、入出力についてそれ以外何も知らなくても、役に立つプログ

ラムをかなりたくさん書くことができる。最も単純な例は、入力を出力へ一度に 1文字ずつ複

写(コピー)する次のプログラムである。

 1文字とってくる。

 while(文字がファイルの終りではない)

   読み込んだばかりの文字を出力

   新しい文字をとってくる

これをEmacs-Lispプログラムで表現すると、次のようになる。

(require 'stdio)

;; 入力を出力に複写;第1版

(defun main ()

(let ((c (getchar)))

(while (not (= EOF c))

(putchar c)

(setq c (getchar)))))

notは真偽値を反転させる。tならnilを nilならtを返す。

 キーボードや画面の上で文字として現われるものは、他のすべてのデータと同じく、内部的

には単にビット・パターンとして格納される。そうした文字データを格納した変数は数値型に

なる(正確には、生成された数値オブジェクトにシンボル cがバインドされる。ただし数値は

シンボル内に値をもつことが可能であり、Emacs-Lispではそのように実装されている)。

 EOFはstdio.elで定義されている整数である。しかし、それがどのキャラクタコードとも同

じでないものでありさえすれば、その具体的な数値は問題にする必要がない。記号定数を使う

ことにすれば、プログラムでは具体的な数値には何も左右されないことが保証される。

 複写を行なうプログラムは、経験のある Emacs-Lispプログラマなら実際にもっと短く書ける

であろう。Emacs-LIspでは

 (setq c (getchar))

といった任意の代入文は、式の中に書ける。その値は、単に左辺に代入される値である。cへ

の文字の代入を whileのテスト部分に入れることにすれば、ファイルを複写するプログラムは

次のように書ける。

Page 16: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

(require 'stdio)

;; 入力を出力に複写;第2版

(defun main ()

(let (c)

(while (not (= EOF (setq c (getchar))))

(putchar c))))

 このプログラムでは、文字を一つとって cに代入した後、その文字がファイルの終りの目印

かどうかを whileで調べている。目印でなければ、whileの本体が実行され、その文字が印字

される。ついで、繰り返される入力が最後に終りになると、この whileは終りとなり、したがっ

てmainも終りとなる。

 このプログラムは入力を中心にすえている──いまや getcharの呼出しは一つしかない──

から、短くなっている。テストの中に代入文を埋め込むことは、Emacs-Lispに許される貴重な

圧縮記法の一つである。このスタイルはこれからもよく出てくる。(ただし、これを使いすぎ

ると、わかりにくいプログラムになってしまうので注意されたい。これはわれわれがなくした

いと思っている傾向である?)

演習 1-6 (= EOF (setq c (getchar)))という式の値が 0か1であることを確認せよ。

 不可能。=はtかnilを返す。

演習 1-7 EOFの値を印字するプログラムを書け。

1.5.2 文字のカウント 次のプログラムは文字数を数えるためのものである。これは複写のプログラムに少し手を加

えたものになっている。

Page 17: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

(require 'stdio)

;; 入力される文字をカウント; 第 1版

(defun main ()

(let ((nc 0))

(while (not (= EOF (getchar)))

(setq nc (1+ nc)))

(printf "%d\n" nc)))

次の文

 (1+ nc)

には新しい関数 1+が出ているが、これは1だけの増分(インクレメント)を表わす。ここは(+

nc 1)と書いてもよいが、(1+ nc)のほうが短くて、しかもより一層能率的なことが多い(nc変

数がレジスタに割り付けられればの話?)。これに対応する関数としては、1だけの減少(デ

クレメント)を表わす1-がある。

 ループを表現するもう一つの別のやり方を示すために、次に whileの代わりにdoを使って書

いてみよう。

(require 'stdio)

;; 入力される文字をカウント; 第 2版

(defun main ()

(do ((nc 0 (1+ nc)))

((= EOF (getchar)) (printf "%d\n" nc))))

 ここではdoループの本体は空である。これはすべての仕事が、テストと再初期化の部分で行

なわれているからである。

 この文字を数えるというプログラムを離れる前に注意しておきたいことは、入力に何も文字

がなければ、最初に getcharを呼ぶときにwhileのテスト結果なら偽、doのテスト結果なら真

となるから、このプログラムでゼロという当然の答が得られることである。whileや doのいい

点の一つは、ループの本体に入る前に、その頭?のところでテストが行なわれることにある。

つまり、何もすることがなければ、ループの本体に入ることなく、何も行なわれないのである。

"文字なし"というような入力がきたときでも、プログラムはちゃんと対応できるものでなけれ

ばならない。while文と do文は、境界条件においてもきちんとした処理が行なわれることを保

証しているといえる。

1.5.3 行数のカウント 次のプログラムは入力の行数を数えるものである。上に述べたように、標準ライブラリでは、

各入力ストリームが、改行で区切られた一連の行の形になることが保証される。したがって、

Page 18: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

行を数えるには、改行記号の数を数えればよい。

(require 'stdio)

;; 入力の行数をカウント

(defun main ()

(let (c (nl 0))

(while (not (= EOF (setq c (getchar))))

(if (= ?\n c)

(setq nl (1+ nl))))

(printf "%d\n" nl)))

 この whileの本体は、一つのifであり、これは、カウントアップ(setq nl (1+ nl))の制御

に使われている。この if文では、カッコ内の条件を調べ、それが真であれば、次に続く文を実

行する。ここでも何が何を制御しているかを示すために字下げを行なった(字下げは Emacsが

適当にやってくれる)。

 ここで等号=は、(Pascalの単一の=あるいは Fortranの.EQ.と同じく)"に等しい"ことを表

わす Emacs-Lispの関数である。C言語の初心者は==のつもりで=とつい書いてしまうことがあ

るが、Emacs-Lispにおいてはそういうことはあり得ない。

 次に、機械の文字セットの中において、文字の内部表現に等しい値を求めるには、単一の?に

続けて任意の 1文字を書けばよい。これを文字定数と呼ぶが、これは小さな整数を書くもう一

つのやり方に他ならない。例えば、?Aは文字定数である。ASCII文字セットの中では、この値

は 65であり、これが文字 Aの内部表現になっている。もちろん、65と書くよりは?Aのほうが

よい。意味がはっきりするし、特定の文字セットとは独立だからである。

 文字列で使われるエスケープ文字は文字定数の中でも有効である。したがって、判定文や式

の中でも、?\nは改行コードの値を表わす。注意すべきことは、?\nが一つの文字であることで、

式の中でこれは一つの整数と等価である。一方、"\n"はたまたま一つの文字だけを含む文字列

である。文字列と文字については、第2章でさらに論ずる。

Page 19: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-8 空白、タブ、改行を数えるプログラムを書け。

Page 20: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-9 二つ以上の空白を一つの空白に置き換えながら、入力を出力に複写するプログラム

を書け。

Page 21: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-10 各タブを\tに、各バックスペースを\bに、各バックスラッシュを\\に置き換えな

がら、入力を出力に複写するプログラムを書け。こうすれば、タブとバックスペースははっき

り目に見えるようになる。

 バックスペースの可視化は処理してはいるが不可能。(これはダム端末の発想?^^;)

Page 22: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

1.5.4 単語のカウント 次に、第四の有用なプログラムとして、行と単語と文字の個数を数えるプログラムを考えて

みよう。ただし、ここでは単語は、空白やタブや改行を含まない任意の文字列であるとゆるや

かに定義しておく。このプログラムは、UNIXのユーティリティ・プログラム wcの裸の骨格部

分である。

(require 'stdio)

(defconst IN 1)

(defconst OUT 0)

;; 入力中の行、単語、文字のカウント

(defun main ()

(let (c (nl 0) (nw 0) (nc 0) (state OUT))

(while (not (= EOF (setq c (getchar))))

(setq nc (1+ nc))

(if (= ?\n c)

(setq nl (1+ nl)))

(if (or (= ? c) (= ?\n c) (= ?\t c))

(setq state OUT)

(if (= OUT state)

(progn

(setq state IN)

(setq nw (1+ nw))))))

(printf "%d %d %d\n" nl nw nc)))

 このプログラムでは、単語の最初の文字がくるたびに、カウントを 1進めている。変数

stateは、現在見ているのが単語の中かどうかを示すもので、最初は"単語の中ではない"から、

値OUTを割り当てておく。プログラムを読みやすくするうえからいうと、リテラル値 1および 0

を使うよりは、記号定数 INおよび OUTを使うほうが好ましいであろう。もちろん、こんな小さ

なプログラムではどちらでもいいことだが、大きなプログラムでは、わかりやすさということ

を考えると、はじめからこういう風に書くちょっとした労力は十分払うに値するのである。ま

た、数を記号定数としてだけ表わすようにしておけば、プログラムに広範な変更を加えるのが、

よりたやすいこともわかるだろう。

 次に

 (let (c (nl 0) (nw 0) (nc 0) (state OUT))

という行は、letフォームのローカルバインドを初期化する。これは letの本体部分で、次の

ように書くのと同じである。

Page 23: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

 (setq nl 0 nw 0 nc 0)

 一方、orは ORを意味するから

 (if (or (= ? c) (= ?\n c) (= ?\t c))

という行は、"cが空白あるいはcが改行あるいはcがタブならば"の意である。(エスケープ文

字列\tはタブ文字を目に見える形にした表現であることを思い出そう。)これに対応する AND

はandである。andでつながれた式は、順番に評価され、真偽が判明すると、ただちに評価を

止めることになっている。したがって、cが空白であれば、それが改行かタブであるかどうか

は調べる必要がないから、そのチェックは行なわれない。これはここでは重要なことではない

が、すぐにわかるように、もっと複雑な場合には非常に重要となる。

 この文には、else文も出てくる("else"の表記はないが、ifの三番目のリストは elseとし

て動作する)が、これは、ifの条件式が偽のときに行なうべき代替行為を指定するものである。

一般形式は次の形である。

 (if (式 1)

  式 2

  (式 3・・・)

 ifで指定される式 2、式 3のうち、実行されるものはどちらか一方だけである。式 1が真な

らば式2が実行され、そうでなければ式 3(暗黙のprogn)が実行される。どちらの文ももっと

複雑にすることが可能である。先の単語数をカウントするプログラムは、三つ目の ifで上記式

2にあたる部分をprognを使って書いている。これは式2には一個のリストしか書けないからで

ある。

演習 1-11 単語カウント・プログラムのテストは、どのようにするか?もしバグがあるとした

ら、それをあばき出すにはどんな入力をすればよいか?

 空白、改行、タブが1個、2個、3個以上連続するケースをそれぞれテストする。

 (プログラムの制約によるが)入力にバックスペース文字を含むケースをテストする。

Page 24: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-12 入力した単語を1行に一つずつ印字するプログラムを書け。

Page 25: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-13 "単語の定義"をもっとはっきりさせて単語数をカウントするプログラムを書け。例

えば、英字で始まり、英字・数字・アポストロフィの連続したものを"単語”とするというよう

に。

Page 26: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

1.6 配列 個々の数字と空白文字(ブランク、タブ、改行文字)とその他のすべての文字の出現する回

数を数えるプログラムを書いてみよう。これは少しわざとらしい例だが、この一つのプログラ

ムでEmacs-Lispのいくつかの面を示すことができるからである。

 入力に関してここでは 12のカテゴリーがあるとするから、それぞれの数字の出現する回数を

把握するには10個の異なった変数を使うより、配列を使ったほうが便利であろう。ここに一つ

のプログラム形式を示す。

(require 'cl)

(require 'stdio)

;; 数字、空白、その他をカウント

(defun main ()

(let (c (nwhite 0) (nother 0) (ndigit [0 0 0 0 0 0 0 0 0 0]))

(fillarray ndigit 0)

(while (not (= EOF (setq c (getchar))))

(if (and (>= c ?0) (<= c ?9))

(aset ndigit (- c ?0) (1+ (aref ndigit (- c ?0))))

(if (or (= c ? ) (= c ?\n) (= c ?\t))

(setq nwhite (1+ nwhite))

(setq nother (1+ nother)))))

(printf "digits =")

(do ((i 0 (1+ i)))

((<= 10 i))

(printf " %d" (aref ndigit i)))

(printf ", white space = %d, other = %d\n" nwhite nother)))

自分自身に対するこのプログラムの出力は次のようになる。

 digits = 18 5 0 0 0 0 0 0 0 1, white space = 149, other = 361

 宣 言

 (ndigit [0 0 0 0 0 0 0 0 0 0])

は ndigitが 10個の配列であることを宣言している。Emacs-Lispでは、配列のインデックスは

常に 0から始まるため、その要素は 0、1、…、9になる。これは初期化と、配列の印字との do

ループにはっきり現われている。

 インデックスは任意の整数式であり、もちろん iのような整数の変数とか定数を含んでもか

まわない。

 この特定のプログラムは。数字の文字表現の性質に完全に依存するものである。

Page 27: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

例えば、テスト

 (if (and (>= c ?0) (<= c ?9))

では、c中の文字が数字かどうか調べ、そうであれば、

 (- c ?0)

でその数値が求まる。この式は?0、?1、…、?9が連続して増加してゆく値をとるときにのみ有

効である。幸いこれは通常の文字セットすべてに当てはまる。

 文字が数字か、空白文字か、その他か、を決定するのは次のシーケンスである。

(if (and (>= c ?0) (<= c ?9))

(aset ndigit (- c ?0) (1+ (aref ndigit (- c ?0))))

(if (or (= c ? ) (= c ?\n) (= c ?\t))

(setq nwhite (1+ nwhite))

(setq nother (1+ nother))))

 パターン

 (if 条件 1

  文 1

  (if 条件 2

  文 2

  文 3)

は多分岐の判断を表現する方法であるが、こういう場合は通常 condを用いることが多い。この

プログラムはある条件が満足されるまで上から順に読まれ、条件が満足されたところでそれに

対応する文が実行され、それでこの構文はすべて終了となる。(もちろん文は復文であっても

よい。)もしいずれの条件も満足されなかった場合には、最後の文 3が、存在すれば実行され

る。最後の文 3が省略されていれば(単語数カウント・プログラムのように)何も実行されな

い。最初のifと最後の文との間にはいくつもの

 (if 条件

  文

というグループがあってもかまわない。スタイルとしては、長い判断文でもページの右側には

み出ることはないように、この構文を前記のように書くのが賢明である。

 数多くの条件に分岐するもう一つの方法としては、第 3章で述べるcond文というのがある。

対照のために、第 3章でこのプログラムのcond版を示すことにする。

Page 28: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-14 入力した単語の長さをヒストグラム(度数分布図)にしてプリントするプログラム

を書け。ヒストグラムは横に書くほうが簡単だが、縦書きに挑戦してみるのもよい。

 縦書きは当然パス(笑)。

Page 29: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

1.7 関数 Emacs-Lispの関数は、Fortranのサブルーチンあるいは関数、Pascalの手続きあるいは関数

に対応する(?)。関数はその中身に関して、何も心配しないで使えるようにある処理をブラック

ボックスの中につめ込むのに便利な方法である。関数は実際、大きなプログラムに潜在する複

雑さに対処するただ一つの方法となる。うまく設計された関数に関しては、いかに処理される

のかということを無視して、何が出来るのかを知っていれば十分である。Emacs-Lispは関数の

使用が、やさしく、便利で、かつ能率的であるように設計されている。たった 2〜3行しかなく

て、1回しか呼ばれない関数を見かけることがよくあるが、それは断片的なプログラムをわか

りやすく、はっきりとさせるために使われているのである。

 これまで、われわれは定義済みの標準入出力関数としては putchar、getchar、printfのみを

使った。そこで今度は自作の関数を二、三書いてみることにしよう。Emacs-Lispには Fortran

にある**のような指数演算子がないから、整数 mを正整数nでベキ乗する(power m n)という

館数を書いて、関数定義の機構を示すことにする。例えば、(power 2 5)の答は 32と出せるよ

うにしたい。この関数は小さな整数の正のベキ乗しか扱えないので、実用的なベキ乗ルーチン

ではないが、説明用には十分であろう。

 以下に、関数 powerとそれをテストする主プログラムを示す。全体の構造は一度に見ること

ができるであろう。

(require 'cl)

(require 'stdio)

;; ベキ乗関数をテストする

(defun main ()

(do ((i 0 (1+ i)))

((<= 10 i))

(printf "%d %d %d\n" i (power 2 i) (power -3 i)))

0)

;; power: baseを nのベキ乗する; n >= 0

(defun power (base n)

(do ((i 1 (1+ i))

(p 1 (* p base)))

((< n i) p)))

 どちらの関数も次のように同じ形をしている。

(defun function-name (parameter declarations, if any)

statements)

 これらの関数はどんな順序で現われてもよく、一つのソース・ファイルにまとめても二つ以

上に分割してもかまわない。もちろんいくつかのソース・ファイルにした場合は、一つのとき

Page 30: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

よりもロードに手間がかかる。しかしそれは言語の性質も問題ではなく、OSの問題である(?)。

さしあたって今は、両方の関数は同一ファイル中にあるものと仮定する。このためEmacs-Lisp

プログラムの走らせ方についていままで学んだことは何一つ変わらない。

 さて、関数powerは、次の行によりmainで 2回呼び出されている。

 (printf "%d %d %d\n" i (power 2 i) (power -3 i))

どちらの呼出しでも二つの引数を powerに渡していて、その度に書式付きで印字されるべき整

数の値が返されている。式の中では、(power 2 i)は、2や iが整数であるのと同様に整数であ

る。(すべての関数が整数の値をとるわけではない。これについては、第 4章で取り上げる。)

 power自体の最初の行

 (defun power (base n)

はパラメータの名前の宣言になっている。パラメータを表わすのに、power内で使われる名前

は純粋に powerにだけ使われる局所的なものであり、他の関数からアクセスすることはできな

い。したがって、他のルーチンでは、矛盾することなしに同じ名称を使うことができる。これ

は変数i、pについてもそうであり、power内のiとmain内のiは無関係である。

 われわれは関数定義の中のカッコ内にリストされた変数を一般にパラメータと呼び、関数呼

出しに使われる値を引数(argument)という。同じ区別を行なうのに、仮引数および実引数とい

うことばが使われることもある。

 powerで計算した値 pは、do文によってmainに返される。返却値のところにはどんな式を書

いてもよい

 ((< n i) p)

関数からは必ずしも値を返す必要はないが、最後に評価した式の値が呼び出し側に返される。

これは関数の終わりのところで"端から落ちる”のと同様である。また呼出しを行なう関数側で

は関数の戻す値を無視することもできる。

 mainは他の関数と同じく関数であるから、その呼出し側に対して値を返すことができる。こ

の場合、呼出し側とは、そのプログラムが実行される環境である。通常、戻り値が 0であれば

正常終了、ゼロでない値は異常な、あるいはエラーの終了状況を表わすのがわかりやすいが、

それは呼出し側の都合に合っていればよい(main関数は最後に評価した式の結果を返す)。簡

単のため、これまでは、main関数から終了状況を表わす値を返していなかったが、今後はそれ

を含めることにする。

Page 31: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-15 1.2節の温度換算プログラムを、変換のための関数を使うように書き直せ。

1.8 引数──参照による呼出し (call by reference) 他の言語、特に Cを使ったことのあるプログラマにとっては、Emacs-Lispの関数には見慣れ

ない面があるかもしれない。Emacs-Lispでは、すべての関数の引数が"参照で"受渡しされるか

らである。これは呼び出された関数には、元の値(オブジェクト)が与えられることを意味す

る。このため、呼ばれたルーチンが元の値にアクセスできない Cのような"call by value(値

による呼出し)"の言語とは、Emacs-Lispの性質は違ったものになっている。

 主な違いは、Emacs-Lispでは、呼ばれた関数が呼んだほうの変数を変えることができるとい

Page 32: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

う点にある。これは変数がデータの器ではなく、シンボルとオブジェクトの関係になっている

ということであり、その振る舞いに注意する必要がある。シンボルについては第 5章で詳しく

述べる。

1.9 文字配列 Emacs-Lispにおける配列の最も一般的な形は、文字の配列である。文字配列の使い方とそれ

を操作する関数を示すために、一群の行を読み込んで、一番長い行をプリントするプログラム

を書いてみよう。プログラムの骨格は単純であり、次のようになる。

 (while (別の行がある)

(if (以前に一番長かった行よりも長い)

その行を格納する

その長さを格納する))

一番長い行をプリントする

 この骨格は、プログラムが自然にいくつかの断片に分かれることをはっきり示している。一

つの断片で新しい行を得、もう一つの断片でそれをテストし、もう一つのでそれを格納し、残

りので処理を制御するのである。

 こうして全体がうまく分けられるから、この形でプログラムも書くことにしよう。まず最初

に、次の行を入力する関数 getlineを書く。他のプログラムの関数としても有効なように、こ

の関数はできるだけ柔軟な形に書くことにする。getlineでは、最低でも、ファイルの終了を

表わす記号は返さなければならない。さらに一般的に有効であるようにプログラムを設計する

なら、行の長さを返し、ファイルの終わりでは 0を返すようにする必要がある。0というのは

決して正しい行の長さではないからファイルの終わりとして使ってよい。どの行にも最低 1文

字あるはずだし、改行文字だけの行でも長さは1になるはずだからである。

 次に、前に一番長かった行よりも長い行が見つかったら、それをどこかに格納しておかねば

ならない。ここで2番目の関数copyが登場する。これは、その新しい行を安全なところへコピー

しておくためのものである。

 最後に、getlineと copyを制御するmainプログラムが必要である。以上の結果を示すと次の

ようになる。

(require 'cl)

(require 'stdio)

(defconst MAXLINE 1000)

Page 33: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

;; 最も長い入力行を印字する

(defun main ()

(let (line (max 0) longest)

(while (< 0 (car (setq line (getline MAXLINE))))

(if (< max (car line))

(progn

(setq max (car line))

(setq longest (copy (cadr line))))))

(if (< 0 max)

(printf "%s" longest))

0))

;; getline: 入力行と長さを返す

(defun getline (lim)

(let ((i 0) c (s (make-string 0 0)))

(while (and (< i (1- lim))

(not (= EOF (setq c (getchar))))

(not (= c ?\n)))

(setq s (concat s (char-to-string c)))

(setq i (1+ i)))

(if (= c ?\n)

(progn

(setq s (concat s (make-string 1 ?\n)))

(setq i (1+ i))))

(list i s)))

;; copy: 文字列をコピーする

(defun copy (string)

(copy-sequence string))

 関数 getlineおよびcopyは一つのファイルに入っているとした。もし、別のファイルに入れ

るのであれば、そのファイル内でprovide宣言し、requireで読み込むことになる。

 getlineでは呼び出し元へ値を返すのにリストを使っている。最後の行(list i s)でリスト

を作成し、それが呼出し元へ返される。Emacs-Lispの関数は最後に実行した結果が返されるの

である。

 したがって Emacs-Lisp の関数は必ず何かの値を返すことになる。copyの戻り値は copy-

sequenceによって作られる新たな文字列オブジェクトである。

 このプログラムを離れるに当たって、こういった小さなプログラムには、幾分まずい点があ

ることをお断りしておく。例えば、行数の制限よりも大きな行を読み込んだときに mainではど

うすべきか?getlineは文字数が MAXLINEになると入力を打ち切って、文字列の終わりに改行

を付加し、安全に動作する。改行を付加しなければ、mainでは返ってきた長さと最後の文字を

Page 34: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

見て、行が長過ぎたかどうかを判断し、それによって対処することが可能である。われわれは、

話を簡単にするため、この問題は無視した。

 getlineのユーザとしては、前もって入力行の長さを知る方法はないが、Emacs-Lispの文字

列オブジェクトは予め領域を確保して作られるわけではないため、オーバーフローが起きるこ

とはない。copyにおいて新たに作成される文字列オブジェクトも同様である。

Page 35: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-16 一番長い行を印字するプログラムの mainルーチンを書き直して、任意の長さの入

力行群の長さ、およびテキストの出来るだけ多くの部分を正しく印字できるようにせよ。

Page 36: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-17 80文字より長い行をすべて印字するプログラムを書け。

(ロジックは同じなので、40文字より長い行とする)

Page 37: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-18 各入力行から、行の後ろのプランクやタブを取り除き、かつ空白行はすべて削除す

るようなプログラムを書け。

Page 38: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-19 文字列 sを逆に並べる関数copy-reverseを書け。さらに、この関数を使って、入力

を一時に1行ずつ逆転するプログラムを書け。

Page 39: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

1.10 グローバル変数と適用範囲 mainの中の変数(line、longestなど)は、mainに固有のものであり、局所的なものである。

というのは、それらは main関数の内部で宣言され、以降の文脈でしか直接アクセス出来ないか

らである。他の関数内にある変数も同様である。例えば getlineにある変数iは、copyのiと

は何の関係もない。ルーチン内部の局所変数は、関数が呼ばれたときのみ存在し、関数から呼

出し元へ制御が戻れば消えてしまう。このような変数が他の言語にならって、通常、ローカル

変数とも呼ばれているのはこのためである。

 ローカル変数は、関数の呼出しによって現れたり、消えたりするから、一度呼び出されてか

ら次に呼び出されるまでの間、それらの値は保持されない。そのため、それぞれの関数の入口

で、値をはっきりとセットしなければならない。セットしなければ、値は voidとなり、たとえ

その関数内であっても参照するとエラーになる。ローカル変数の代わりには、すべての関数に

対してグローバルな変数を宣言することが出来る。これはすなわち、任意の関数から名前によっ

てアクセスできる広域変数である。(この機構は、Fortranの COMMONや一番外側のブロックで

定義された Pascal変数に似ている。)グローバル変数は広域的にアクセスできるから、関数間

のデータのやりとりの目的に、引数リストの代わりに用いられる。さらに、グローバル変数は、

関数が呼び出されたり終了したりするごとに現れたり消えたりするのではなく、永久的に存在

するため、それをセットした関数が用済みになった後もその値は残ったままである。

 グローバル変数は任意の関数でも、その外側でも定義することができる。しかし、これもロー

カル変数同様、値がセットされていないうちは voidのままであり、参照するとエラーになる。

議論を具体的にするために、line、longest、maxをグローバル変数として、最も長い行を求め

る先のプログラムを書き直してみよう。これには三つの関数すべての呼出しを変更する必要が

ある(?)。

(require 'cl)

(require 'stdio)

(defconst MAXLINE 1000) ; 入力の最大行数

(defvar max) ; いままで出てきた最大長

(defvar line) ; 現在の入力行

(defvar longest) ; 格納されている最長行

;; 最も長い入力行を印字する ; 特別版

(defun main ()

(setq max 0)

(let (len)

(while (< 0 (setq len (getline)))

(and (< max len) (progn (setq max len)

(copy))))

(and (< 0 max) (printf "%s" longest)))

0)

Page 40: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

;; getline: 入力行と長さを返す ; 特別版

(defun getline ()

(setq line (make-string 0 0))

(let ((i 0) c)

(while (and (< i (1- MAXLINE))

(not (= EOF (setq c (getchar))))

(not (= c ?\n)))

(setq line (concat line (char-to-string c)))

(setq i (1+ i)))

(and (= c ?\n) (progn (setq line (concat line (make-string 1 ?\n)))

(setq i (1+ i))))

i))

;; copy: 文字列をコピーする ; 特別版

(defun copy ()

(setq longest (copy-sequence line)))

main、getline、copyにおけるグローバル変数は先の例の最初の数行で定義されている。構文

的にはdefvarを使用したものになっているが、これはわかりやすくするために記述したもので

あり、max、line、longestのいずれも最初に必要となったところで、いきなり使用しても問題

ない。

 プログラムがいくつかのソース・ファイルに分かれていて、ある変数を、例えばファイル 1

で定義してファイル 2とファイル 3で使う場合、変数の使用を結び付けるのには、ファイル 2

とファイル 3にあるプログラムが動き出す前に、ファイル1がロードされていればよい。

 これまで、グローバル変数やローカル変数を参照するのに、宣言や定義という言葉を使用し

てきているが、これらは厳密には他の言語と意味が異なる。変数とは、その名前であるシンボ

ルと、それにバインドされるオブジェクト(値)を組み合わせたものである。Emacs-Lispでは

何かの変数を、例えば整数型として定義するということが出来ない。そうではなく、任意の数

値オブジェクトを、任意のシンボルにバインドすることにより整数を扱う変数として機能する

ようになる。つまり、何らかのルールで記憶域を予め割り当てておくということが出来ないの

だ。

 ところで、やりとりが簡単に思える──引数リストが短く、必要なときには変数がいつも存

在している──ためか、見えるものすべてをグローバル変数にしてしまおうとする傾向がある。

しかしグローバル変数は、必要のないときでも常に存在しているものである。このコーディン

グ形式には危険な落とし穴がいくつかある。というのは、データ結合の不明確なプログラムを

つくったり、そのため変数が予期しないときとか、うっかりしたときに変更されてしまったり、

必要が生じても後からは変更しにくいプログラムになってしまったりすることがあるからであ

る。一番長い行を印字するプログラムの第 2版は、この意味においても、また、操作する変数

の名前を組み入れることで、二つの非常に有用な関数の汎用性を壊しているという意味におい

ても、最初の版に比べて劣っている。

Page 41: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

 さて、ここまでで、Emacs-Lispで普通に使われる中核部分とでも呼ぶべきところはカバーし

た。いままで述べた一握りのビルディング・ブロックを使えば、かなりな大きさの実用的なプ

ログラムを書くことができるから、ここで少し休んで、十分よく考えてから先に進むのが賢明

であろう。次の演習問題は、本章の始めのほうで示したプログラムよりもかなり複雑なプログ

ラムを想定している。

Page 42: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-20 入力されたタブを、次のタブ・ストップまでのスペースをうめる適当な数のブラン

ク(空白)で置き換えるプログラム detabを書け。タブ・ストップの位置は、例えば n文字ご

とというように固定して考えよ。nは変数にすべきか、記号パラメータにすべきか?

(タブ・ストップはEmacs上で認識されるため意識しないことにする)

Page 43: 第1章 やさしい入門nobulign.way-nifty.com/Lisp/KREL.pdf第1章 やさしい入門 まずは、Emacs-Lispを手短に紹介することから始める。われわれの目的は、細かい点や、公

演習 1-21