コマンド作成の基礎
Ver 0.1 (5/20/98)

目次

  1. 関数を作る
  2. 関数をコマンドとして認識させ実行する
  3. コマンドの改良(バッファに表示する)
  4. おまけ(プログラムの抽象化による改良)

1.関数を作る

コマンドを作るのはとても簡単.Lispで関数を書けば,それにちょっとキーワードを足すだけで,コマンドが作れてしまうのです.

それでは,まず関数を作成しましょう.ここでは,時刻,ユーザのフルネーム,ユーザのメールアドレスをリストにして返すstamp関数を作成します.

現在時刻を返す組み込み関数は,current-time-string.ユーザのフルネームを返す関数は,user-full-name.ユーザのメールアドレスを保持している変数は,user-mail-addressです.これらの関数と変数は,Emacs Lispシステムにより,始めから提供されています.どのような関数が提供されているかは,infoマニュアルを見て勉強してください.

以下が,stamp関数のコードです.簡単ですね.

stamp()関数
  (defun stamp()
    (list (current-time-string) (user-full-name) user-mail-address) )
  
これをEmacs Lispインタープリターで評価してみましょう.M-x lisp-interaction-modeで,バッファをS式づつ評価できるようになります.上のコードを入力したら,C-jをタイプします.これで以下の結果を得るはずです.
stamp()関数の評価結果
  ("Sun May 10 23:11:43 1998" "Taro Nishimura" "taro@nishimura.homelan.or.jp")
  
それでは,次の節で,この関数をEmacsのコマンドとして起動出来るようにしましょう.


2.関数をコマンドとして認識させ実行する

関数をコマンドとして認識させるのは,とても難しいのです.用意はよろしいでしょうか?よく聞いてください.
「関数定義の前に(interactive)を加えるだけです」
どうです,あっけなくて危うく聞き逃しそうになったのではないでしょうか?それでは,先ほどのstamp関数を書き換えてみましょう.
stampコマンドの作成
  (defun stamp()
    (interactive)
    (list (current-time-string) (user-full-name) user-mail-address) )
  
これを,インタープリターに評価させてみましょう.
stamp()関数の評価結果
  stamp
  
こんどは,コマンド名stampが返りました.それでは,M-x stamまで入力してTabキーを押してみましょう.コマンド名補完が起こりstampになるでしょう.後は,リターンキーを押せばコマンドが実行されます.

そうです,何も起こりませんね.なぜなら,先ほどのコードでは,リストを作成するだけで,バッファに表示させるコードは書かなかったからです.コマンドを実行するのは,Emacs上であり,インタープリタでLisp関数を評価しているわけではありません.したがって,(list .......)が評価されても,その値が画面に表示されないのです.

それでは,次の節でもう少し改良し,リストの内容をバッファに表示させてみましょう.


3.コマンドの改良(バッファに表示する)

stampコマンドは,("Sun May 10 23:11:43 1998" "Taro Nishimura" "taro@nishimura.homelan.or.jp")というリストを作成しました.このリストの要素を順に1行づつ表示してみましょう.さらに,これらの行を=====で囲んでみましょう.

まず,アルゴリズムを示します.

  1. 文字列を要素に持つリストの,各要素の文字数からなるリストを構築する.
    文字列の長さを調べる組込み関数は,length.これはリストの要素数を調べるときにも使う.
  2. リストの要素の文字数で,最大のものを保持する.
  3. その最大文字数分の=と改行を表示.
    make-string関数は組み込み関数で,(make-string num ?a)でnum個のaで構成される文字列を返す.
  4. (時刻,氏名,アドレス)リストのcarを順に表示して改行.
  5. 最大文字数分の=と改行を表示.
stampコマンドの改良
  (defun stamp()
    (interactive)
    (setq stamplist 
          (list (current-time-string) (user-full-name) user-mail-address) )
    (let* ((time (length (car stamplist)))
           (name (length (car (cdr stamplist))))
	   (mail (length (car (cdr (cdr stamplist))))))
	   (setq maxchar (cond
                           ( (>= time name) 
                               (cond
                                 ( (>= time mail) time )
                                 ( t mail ) ))
                           ( (>= name mail) name )
                           ( t mail ) )))
    (insert (make-string maxchar ?=))    (insert ?\n)
    (insert (car stamplist))             (insert ?\n)
    (insert (car (cdr stamplist)))       (insert ?\n)
    (insert (car (cdr (cdr stamplist)))) (insert ?\n)
    (insert (make-string maxchar ?=))    (insert ?\n) )
  
これをLisp InteractionモードのバッファでC-jで評価し,M-x stampで実行してみる.以下の結果が表示されればOK.もちろん,名前や時刻は異なるでしょうけど.
改良したstampコマンドの実行結果
  ==========================
  Mon May 11 01:04:26 1998
  Taro Nishimura
  taro@nishimura.homelan.or.jp
  ==========================
  


4.おまけ(プログラムの抽象化による改良)

上で作成したプログラムは,1つのコマンドであることを強調するために,あえて1つの関数で書いた.しかし,その動作はアルゴリズムごとに細分化できるので,それぞれの手順をモジュール化した方が,コードの再利用の面からも,プログラムの抽象化の面からも望ましい.と,言うことで,ここでは,上のプログラムをもう少し改良してみよう.せっかくlispを使っているので出来るだけ再帰を用いて作成してみよう.

もう一度アルゴリズムを示し,それに応じた関数を作っていこう.

  1. 文字列を要素に持つリストの,各要素の文字数からなるリストを構築する.
    char_and_num関数を作り,そこからlist_cdr関数で文字数からなるリストを作るのでは なく,始めから直接,文字列からなるリストから,文字数を取り 出し,文字数のみからなる関数を作った方が,プログラムも簡潔で,実行速度も速く なる. しかし,それでは与えられる文字列のリスト構造に依存してプログラムを書き換えなく ては行けないので,入子になったリストでも,対応する文字列と文字数を一つにまとめ たconsセルを作成できるようにしておけば,文字列と文字数を用いるプログラムで 再利用できる.
  2. リストの要素の文字数で,最大のものを保持する.
    ここでは,数字で構成されるリストの中で最大のものを返す関数largest_num を定義する.
    largest関数
      (defun largest( numlist )
        (cond ((null numlist) 0) 
              ((atom (car numlist)) 
                 (cond ((> (car numlist) (largest (cdr numlist))) (car numlist))
                       (t (largest (cdr numlist)))) )
              ((> (largest (car numlist)) (largest (cdr numlist))) (largest (car numlist)))
              (t (largest (cdr numlist))) ) )
      
    この関数は,(4 8 3 (3 4 11 (6 8 9)) 2)の様に,入れ子になったリストでも最大の 数を返す.この関数も,char_and_num関数と同様に,quoteでくくったリストを渡す べきではない.
  3. その最大文字数分の=と改行を表示. make-string関数は組み込み関数で,(make-string num ?a)でnum個のaで構成される 文字列を返す.
  4. (時刻,氏名,アドレス)リストのcarを表示して改行し, 最大文字数分の=と改行を表示.
    ここで,改行を表示するfeedline関数,文字列を要素とするリストの要素を順に 1行づつ表示していくlist_println関数を作成する.
    feedline関数
      (defun feedline() (insert ?\n))
      
      list_println関数
      
    (defun list_println( mylist ) (cond ((char-or-string-p (car mylist)) (insert (car mylist)) (feedline) (list_println (cdr mylist)) ) ((> (length mylist) 1) (list_println (car mylist)) (list_println (cdr mylist))) ))
これで,stampコマンド自体は簡潔に書ける.
stampコマンドの抽象化(stam2)
  (defun stamp2()
    (interactive)
    (setq stamplist 
          (list (current-time-string) (user-full-name) user-mail-address) )
    (let* ((maxchar (largest (list_cdr (char_and_num stamplist))))
           (mylist (list (make-string maxchar ?=) stamplist (make-string maxchar ?=))) )
          (list_println mylist) ) )
  
それでは,ここでもう一度,stampコマンドを抽象化するために作成した関数と,抽象化されたstampコマンド,すなわち,stamp2をまとめよう.まとめてみると分かるが,コードの再利用は,このバージョンの方がよいが,この程度のコマンドにこれだけのコードを書くのははっきり言って非効率だと思う.したがって,くれぐれも真似されないようにして頂きたい.(笑)
stampコマンドの抽象化(stam2)のまとめ
  (defun char_and_num(charlist)
    (cond ((null charlist) (cons () '0))
          ((atom charlist) (cons charlist (length charlist)))
          ((= (length charlist) 1)
             (cond ((atom (car charlist))
                      (cons (car charlist) (length (car charlist))) )
                   (t (char_and_num (car charlist))) ) )
          ((= (length charlist) 2)
             (list (char_and_num (car charlist)) (char_and_num(cdr charlist))) )
          (t (cons (char_and_num (car charlist)) (char_and_num(cdr charlist))) ) ))
  
  (defun list_cdr( mylist )
    (cond ((null mylist) ())
          ((atom (cdr mylist)) (list (cdr mylist)))
          (t (list_cdr2 mylist))))
  
  (defun list_cdr2( mylist )
    (cond ((null mylist) ()) 
          ((null (cdr mylist)) (cdr (car mylist)))
          ((atom (cdr mylist)) (cdr mylist))
          (t (cons (list_cdr2 (car mylist)) 
                   (cond ((atom(list_cdr2 (cdr mylist))) 
                            (list (list_cdr2 (cdr mylist))))
                         (t (list_cdr2 (cdr mylist)))) )) ))
  
  (defun largest( numlist )
    (cond ((null numlist) 0) 
          ((atom (car numlist)) 
             (cond ((> (car numlist) (largest (cdr numlist))) (car numlist))
                   (t (largest (cdr numlist)))) )
          ((> (largest (car numlist)) (largest (cdr numlist))) (largest (car numlist)))
          (t (largest (cdr numlist))) ) )
  
  (defun feedline() (insert ?\n))
  
  (defun list_println( mylist )
    (cond ((char-or-string-p (car mylist))
             (insert (car mylist)) 
             (feedline) 
             (list_println (cdr mylist)) )
          ((> (length mylist) 1) 
             (list_println (car mylist)) (list_println (cdr mylist))) ))
  
  (defun stamp2()
    (interactive)
    (setq stamplist 
          (list (current-time-string) (user-full-name) user-mail-address) )
    (let* ((maxchar (largest (list_cdr (char_and_num stamplist))))
           (mylist (list (make-string maxchar ?=) stamplist (make-string maxchar ?=))) )
          (list_println mylist) ) )

  以下が実行結果(M-x eval-bufferのあと,M-x stamp2を実行する).
  ==========================
  Wed May 20 02:12:12 1998
  Taro Nishimura
  taro@nishimura.homelan.or.jp
  ==========================
  
おつかれさまでした.


[ ホームへ | ハッキングのトップへ | Lispのトップへ | Emacs Lispのトップへ ]