« July 2014 | Main | September 2014 »

August 30, 2014

プログラミングを始めるには(33)

あなたは幸運にもelispに興味を持ってプログラミングを始めてみたが、まだ最初のうちはあまり上手くいかないのと同樣に、私にもまだまだelispで書いたことがない、または書けないプログラムがいろいろある。その一つが他のプロセスと通信するプログラムだ。このところ每日投稿していたのが急に失速したのはネタ切れではなく、その通信相手としてまずgnupackを入れてみたがダメ(何がダメなんだ?!と思われる方も多いかもしれないが詳細は省畧)で、パソコンを買い替える度に結果的に入れる羽目になるCygwinをインストールしていたからだ。ちょっと動作的におかしいところもあるが、一應Windows8.1のパソコンでもCygwinは動くらしい。

プロセスと通信するということについては、いろいろと面白い話がある。私はいわゆる「交換・傳送系」と言われる分野の開發をしてきた人なので通信には親近感があるが、そうでない專らOA系?の技術者にとっては通信と言うとむずかしいイメージがあるかもしれない。EmacsはむかしからUNIXというOS(オペレーティングシステム)の中で動いてきたエディタであり、他のプログラムと連攜する機能はUNIXのプロセス閒通信を使用したものとなっている。

若い頃は同僚との閒でプロセスって何?という話をするのが好きだった記憶がある。似たような用語にタスク、スレッド、ハンドラなどがあるが、これらの違いをちゃんと說明出來る人がほとんど居なかったのだ。このへんはUNIXWindowsのようなOSの上で動くプログラムしか作ったことがない人には理解出來ない領域なので、說明出來なくても無理はない。プロセスとはリソースを表す單位で、UNIX上のアプリケーションプログラムはすべてプロセスの中で實行される。最近の感覺で言えば、プロセスは假想サーバの中で動く假想マシンのようなものだ。OSに乘っかってプロセス上で動くアプリケーションプログラムは、自身のコードとOSのコードを實行しながら機能を實現する。自身のコードを實行する場合は保護モードで、OSのコードはカーネルモードで動く。アプリケーションがファイルアクセスなどのシステムコールをすると、ソフトウェア割り込みが發生させ、カーネルモードに切り替え、例外が發生しない限り他から割り込まれないような狀態にしてから、OSカーネルのコードを實行する...などの話は本題からあまりに脫線し過ぎなので、このくらいにしておく。

他のプロセスと通信すると言っても、實はむずかしいことではない。みなさんが、WindowsでコマンドプロンプトからDOSコマンドを實行すると、コマンドプロンプトのウインドウに結果が表示されるだろう。當たり前だと思うかもしれないが、これはコマンドプロンプトからdirコマンドを實行すると、dirプログラムの標準出力と、コマンドプロンプトの標準出力が同じになっているからだ。Emacsでも同樣に、Emacsの中からコマンドを實行するとEmacsの中に結果が表示される。Emacsの場合は、起動したプログラムの結果出力に對してちょっかいを出すことが出來るので、それを應用していろんな機能を實現しているのである。

default-process-coding-system
(japanese-shift-jis-dos . japanese-shift-jis-unix)

(shell-command "ls -l /usr/bin/ls.exe" t)
316
-rwxrwxr-x 1 NOBUO 縺ェ縺・121373 Aug 13 04:58 /usr/bin/ls.exe

(let ((default-process-coding-system '(utf-8-unix . utf-8-unix)))
  (shell-command "ls -l /usr/bin/ls.exe" t))
386
-rwxrwxr-x 1 NOBUO なし 121373 Aug 13 04:58 /usr/bin/ls.exe

上の例のように、Windowsパソコン上のEmacsの中だけでelispのプログラムを書いているのなら、あまり文字コードのことを意識する必要はないが、外部のプログラムが出力する文字列を出力する場合は、そういうことまで意識する必要が出てくる。氣にせずにプログラムを書くと文字化けしてしまうことがある。Cygwinが日本語文字列を表示しないように設定すれば良いのかもしれない。今囘はteratermからCygwin接續するときの文字コード設定を參考にして、Cygwin側の設定を變えようとは思わない。

Ws000098


(shell-command "echo $TERM" t)
32
CMD

(shell-command "echo $LANG" t)
32
JPN

(shell-command "(export LANG=C; ls -l Meadow.exe)" t)
55
----------+ 1 NOBUO なし 6205440 Aug 10  2009 Meadow.exe

こういう結果になる環境の文字コードって制御出來るかどうか分からないので、調査したりトライ&エラーを繰り返す氣になれないのだ。なので、Emacs上のdefault-process-coding-systemI/Oどちらもutf-8-unixにするという、ちょっと强引なやり方で濟ませることにする。

| | Comments (0) | TrackBack (0)

August 27, 2014

プログラミングを始めるには(32)

では今囘はお約束通りarglistinteractiveの關係について。lambda式におけるarglistのフルスペックは以下の通り。

(lambda (arg1 &optional arg2 &rest arg3)
  "docstring"
  body...)

上記のlambda式においては、arg1は必須パラメータであり、arg2arg3はオプショナルなパラメータとなる。そしてarg3はリストとして渡される。もちろん呼び出し側が渡さなかった場合は、どちらもnilになる。

((lambda (arg1 &optional arg2 &rest arg3)
   (print arg1)
   (print arg2)
   (print arg3)
   (if arg2
       (if arg3
	   (apply '+ arg1 arg2 arg3)
	 (+ arg1 arg2))
     arg1)) 1 2 3 4 5)
1
2
(3 4 5)
=>15

((lambda (arg1 &optional arg2 &rest arg3)
   (print arg1)
   (print arg2)
   (print arg3)
   (if arg2
       (if arg3
	   (apply '+ arg1 arg2 arg3)
	 (+ arg1 arg2))
     arg1)) 1 2)
1
2
nil
=>3

((lambda (arg1 &optional arg2 &rest arg3)
   (print arg1)
   (print arg2)
   (print arg3)
   (if arg2
       (if arg3
	   (apply '+ arg1 arg2 arg3)
	 (+ arg1 arg2))
     arg1)) 1)
1
nil
nil
=>1

arglistの說明はこれ以上でもこれ以下でもない、まさにこの通りである。applyはこのようなarglistをファンクションに適用するのに使用するビルトインファンクションである。さて、問題なのはinteractiveである。

interactive is a special form in `C source code'.
(interactive args)

Specify a way of parsing arguments for interactive use of a function.
For example, write
  (defun foo (arg) "Doc string" (interactive "p") ...use arg...)
to make ARG be the prefix argument when `foo' is called as a command.
The "call" to `interactive' is actually a declaration rather than a function;
 it tells `call-interactively' how to read arguments
 to pass to the function.
When actually called, `interactive' just returns nil.

The argument of `interactive' is usually a string containing a code letter
 followed by a prompt.  (Some code letters do not use I/O to get
 the argument and do not need prompts.)  To prompt for multiple arguments,
 give a code letter, its prompt, a newline, and another code letter, etc.
 Prompts are passed to format, and may use % escapes to print the
 arguments that have already been read.
If the argument is not a string, it is evaluated to get a list of
 arguments to pass to the function.
Just `(interactive)' means pass no args when calling interactively.

Code letters available are:
a -- Function name: symbol with a function definition.
b -- Name of existing buffer.
B -- Name of buffer, possibly nonexistent.
c -- Character (no input method is used).
C -- Command name: symbol with interactive function definition.
d -- Value of point as number.  Does not do I/O.
D -- Directory name.
e -- Parametrized event (i.e., one that's a list) that invoked this command.
     If used more than once, the Nth `e' returns the Nth parameterized event.
     This skips events that are integers or symbols.
f -- Existing file name.
F -- Possibly nonexistent file name.
G -- Possibly nonexistent file name, defaulting to just directory name.
i -- Ignored, i.e. always nil.  Does not do I/O.
k -- Key sequence (downcase the last event if needed to get a definition).
K -- Key sequence to be redefined (do not downcase the last event).
m -- Value of mark as number.  Does not do I/O.
M -- Any string.  Inherits the current input method.
n -- Number read using minibuffer.
N -- Numeric prefix arg, or if none, do like code `n'.
p -- Prefix arg converted to number.  Does not do I/O.
P -- Prefix arg in raw form.  Does not do I/O.
r -- Region: point and mark as 2 numeric args, smallest first.  Does no I/O.
s -- Any string.  Does not inherit the current input method.
S -- Any symbol.
U -- Mouse up event discarded by a previous k or K argument.
v -- Variable name: symbol that is user-variable-p.
x -- Lisp expression read but not evaluated.
X -- Lisp expression read and evaluated.
z -- Coding system.
Z -- Coding system, nil if no prefix arg.
In addition, if the string begins with `*'
 then an error is signaled if the buffer is read-only.
 This happens before reading any arguments.
If the string begins with `@', then Emacs searches the key sequence
 which invoked the command for its first mouse click (or any other
 event which specifies a window), and selects that window before
 reading any arguments.  You may use both `@' and `*'; they are
 processed in the order that they appear.

interactiveのヘルプを參照すると、上記のように長々と書かれている。知ったかぶりするとボロが出るので腹に落ちていることだけ書くと、必須パラメータとオプショナルパラメータだけなら、普通は上記の指示子を使用出來る。そうでない場合、たとえば候補を表示して入力を促したり、レストパラメータの場合は上記の指示子では入力出來ないので、そのパラメータを受け取る式を書かなければならないようだ。

(defun test-interactive-1 (a)
  (interactive "nnumber=")
  (print a))
=>test-interactive-1

(call-interactively 'test-interactive-1)
;;; ミニバッファから1を入力
1
=>1

(defun test-interactive-2 (a b)
  (interactive "na=\nnb=")
  (print a)
  (print b)
  (+ a b))
=>test-interactive-2

(call-interactively 'test-interactive-2)
;;; ミニバッファから1,2を入力
1
2
=>3

(defun test-interactive-3 (&rest nums)
  (interactive (mapcar 'string-to-number
		       (split-string (read-string "numbers=") " ")))
  (print nums)
  (apply '+ nums))
=>test-interactive-3

(call-interactively 'test-interactive-3)
;;; ミニバッファから"1 2 3 4 5"を入力
(1 2 3 4 5)
=>15

(defun test-interactive-4 (present)
  (interactive (list (completing-read "Please give me "
				      '("orange" "apple" "pear"))))
  (print present)
  (concat (if (string= "pear" present)
	      "I like "
	    "I don't like ")
	  present))
=>test-interactive-4

(call-interactively 'test-interactive-4)
;;; ミニバッファからorangeを補完入力
"orange"
=>"I don't like orange"

いきなりこうやって說明すると、ちょっとハードルが高いかもしれないが、これらの例を參考にエラーを出しながら自分で實行し、いろいろと確認してみると良い。

| | Comments (0) | TrackBack (0)

August 26, 2014

プログラミングを始めるには(31)

ここらでそろそろlambdaのことを書いておく必要があると思う。lispを始めたばかりの人はlambdaをすぐには理解出來ないだろうし、別にそれを知らなくてもプログラム書けてるし...と思うかもしれない。たしかに簡單な編集コマンドを書いているうちは必要ないであろう。

(defun my-plus-func (a b) (+ a b))
=>my-plus-func

(my-plus-func 1 2)
=>3

(symbol-function 'my-plus-func)
=>(lambda (a b) (+ a b))

;--------

(lambda (a b) (+ a b))
=>(lambda (a b) (+ a b))

((lambda (a b) (+ a b)) 1 2)
=>3

;--------

(關數名 パラメータ)
==
(lambda式 パラメータ)

おそらく上記の内容で、さらに上記の式を實際に實行してみて、それでも理解出來ない人は、私と同樣にちょっと時閒がかかるかもしれない。既にlambda式は何度か出てきているが、ほとんどヤケクソ的に使っていることが多かったので、ここらで眞っ當なケースを擧げてみる。

;;; lambda式を使用する眞っ當なケース

(sort '((1 . 2) (2 . 3) (3 . 4) (4 . 1))
      '(lambda (a b)
	 (< (cdr a) (cdr b))))
=>((4 . 1) (1 . 2) (2 . 3) (3 . 4))
==
(defun my-cdr< (a b)
  (< (cdr a) (cdr b)))
=>my-cdr<

(sort '((1 . 2) (2 . 3) (3 . 4) (4 . 1)) 'my-cdr<)
=>((4 . 1) (1 . 2) (2 . 3) (3 . 4))

;--------

(mapcar '(lambda (a)
	   (let ((string (char-to-string a)))
	     (cond ((= ?1 a) (concat string "st"))
		   ((= ?2 a) (concat string "nd"))
		   ((= ?3 a) (concat string "rd"))
		   (t        (concat string "th")))))
	"123456789")
=>("1st" "2nd" "3rd" "4th" "5th" "6th" "7th" "8th" "9th")
==
(defun my-number-string (a)
  (let ((string (char-to-string a)))
    (cond ((= ?1 a) (concat string "st"))
	  ((= ?2 a) (concat string "nd"))
	  ((= ?3 a) (concat string "rd"))
	  (t        (concat string "th")))))
=>my-number-string

(mapcar 'my-number-string "123456789")
=>("1st" "2nd" "3rd" "4th" "5th" "6th" "7th" "8th" "9th")

では、lambdaに對する違和感が取れた?ところで、次囘はarglistinteractiveの關係について述べることとする。

| | Comments (0) | TrackBack (0)

プログラミングを始めるには(30)

あらら、テキトーなことを書いているうちにもう30囘になってしまった。タグを入れるコマンドがあるなら、タグを取り外すコマンドもないと不便かもしれない。

(defun htm-delete-tag (beg end)
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (while (re-search-forward "</*\\(span\\|strong\\|em\\|br\\)[^>]*>" nil t)
	(replace-match "")))))

こっちは至って簡單。正規表現を閒違えなければ、re-search-forwardreplace-patchの繰り返しでタグが消えてくれる。前囘は說明しなかったが、htm-insert-stronghtm-insert-spanhtm-delete-tag3つのコマンドではナローイング(バッファのある部分だけに焦點を當て、殘りの部分を一時的に參照できなくすること)を使用している。そしてナローイングを使うコマンドは必然的に適用範圍をコマンド起動時に受け取る必要がある。それをやってくれるのが(interactive "r")という式だ。これに對應するarglistは(beg end)になる。パラメータの名前はbegendでなくても良い。一應(interactive (list (region-beginning) (region-end)))と書いても良いが、他にもパラメータが必要な場合を除いて、そんなことをしているプログラムは見たことがない。

ナローイングする場合は必ずsave-excursionsave-restrictionを常套句的に使用する。これにより、このフォームを拔けるときにナローイングが解除される(widenされる)。ナローイングのおかげで、關係ないテキストまで變更してしまう心配をせずに濟むのは、精神衞生上素晴らしいことではないだろうか。

| | Comments (0) | TrackBack (0)

August 25, 2014

プログラミングを始めるには(29)

このシリーズの記事を書くようになってから、文章にタグをたくさん入れる必要が出てきた。そろそろ面倒臭くなってきたので、簡單なコマンドを書いて少し樂をしようと思う。

一つは英數字を太斜體にするタグを埋め込むコマンド。もう一つは行閒を變更するタグを埋め込むコマンドである。

(defun htm-insert-strong (beg end)
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (and (re-search-forward "<\\(strong\\|em\\|span\\)" nil t)
	   (if (y-or-n-p "Continue? ")
	       (goto-char (point-min))
	     (goto-char (point-max))))
      (while (re-search-forward "[0-9A-Za-z-]+" nil t)
	(goto-char (match-beginning 0))
	(insert-before-markers "<strong><em>")
	(forward-char (length (match-string 0)))
	(insert-before-markers "</em></strong>")))))

(defun htm-insert-span (beg end)
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (insert-before-markers "<span style=\"line-height:200%\">")
      (goto-char (point-max))
      (insert-before-markers "</span><br>\n<br>"))))

こんな23分もあれば書けそうなしょうもないコマンドでも使ってみると割と便利なものだ。

ちなみに、前の記事でletを使わないプログラムなんて普段書かないと言いつつ書いているし、23分で書けそうなのにデバグまでさせられた。

特に氣を付ける必要があるのはhtm-insert-strongの方。最初はre-search-forwardreplace-matchの組み合わせで簡單に濟まそうと思ったのに、大文字をリプレースするとタグまで大文字になるのが氣になって、マッチ文字列の先頭と終端にそれぞれタグの文字列をinsertする方法に變えたのがアダになった。match-beginningの方は良いとして、match-endが返すポイントはマッチした文字列に對するものなので、タグをインサートした後は意味のないポイントになってしまう。ということで、文字列は普段あまり使わないinsert-before-markersで行ない、その後でマッチした文字列の長さ分forward-charしている。

こんな姑息なことをするくらいなら、マーカを使ってマッチ文字列の先頭と終端のポイントを先に押さえておく、つまりちゃんとletを使えば良かったのだった。

(defun htm-insert-strong (beg end)
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (and (re-search-forward "<\\(strong\\|em\\|span\\)" nil t)
	   (if (y-or-n-p "Continue? ")
	       (goto-char (point-min))
	     (goto-char (point-max))))
      (while (re-search-forward "[0-9A-Za-z-]+" nil t)
	(let ((mark1 (set-marker (make-marker) (match-beginning 0)))
	      (mark2 (set-marker (make-marker) (match-end 0))))
	  (goto-char (marker-position mark1))
	  (insert-before-markers "<strong><em>")
	  (goto-char (marker-position mark2))
	  (insert-before-markers "</em></strong>"))))))

最初からこう書けばよかったのだ(笑)

| | Comments (0) | TrackBack (0)

プログラミングを始めるには(28)

defunの次はletの話。私が普段書くプログラムでletを使わないものはほとんどないと言って良い。

let is a special form in `C source code'.
(let varlist body...)

Bind variables according to varlist then eval body.
The value of the last form in body is returned.
Each element of varlist is a symbol (which is bound to nil)
or a list (SYMBOL VALUEFORM) (which binds SYMBOL to the value of VALUEFORM).
All the VALUEFORMs are evalled before any symbols are bound.

これはシンボルをローカルに(局所的に)定義して、それを使用したprogn(式を順番に評價していって最後の式の結果を返す)を書くためのスペシャルフォームである。letはパラレルバインディング、let*はシリアルバインディングになっている。二つの違いは定義したローカルシンボルを、次のローカルシンボル定義で使用出來るかどうかなので、使用頻度としては必然的にlet*の方が多くなる。

こういう基本的なものについては、眞面目に書くとむずかしくなるだけなので、何か他の似たもので說明すると分かりやすい。

main()
{
  int a = 1;
  int b = 2;
  int c = 3;
  int d = 0;

  d = a + b + c;

  printf("d = %d\n", d);

  return 0;
}

(defun main ()
  (let ((a 1)
	(b 2)
	(c 3)
	(d 0))
    (setq d (+ a b c))
    (message "d = %d\n" d)
    0))

ではこの2つを比較すると、どこが違うのか。Celispでは言語が違うのだから書き方が違うのは當然として、ローカル變數abcdを定義して、abcの合計をdに設定し、それをプリントするという意味ではほとんど同じに見えないだろうか。

main()
{
  int a = 1;
  int b = 2;
  int c = 3;
  int d = 0;

  {
/*     int a = 10; */
    int b = 20;
    int c = 30;
    int d = 0;

    d = a + b + c;

    printf("d = %d\n", d);
  }

  d = a + b + c;

  printf("d = %d\n", d);

  return 0;
}

(let* ((a (let* ((a 1)
		 (b (+ a 1))
		 (c (+ a b 1)))
	    (print (format "a=%d, b=%d, c=%d, total=%d" a b c (+ a b c)))
	    a))
       (b (let* ((a 10)
		 (b (+ a 10))
		 (c (+ a b 10)))
	    (print (format "a=%d, b=%d, c=%d, total=%d" a b c (+ a b c)))
	    b))
       (c (let* ((a 100)
		 (b (+ a 100))
		 (c (+ a b 100)))
	    (print (format "a=%d, b=%d, c=%d, total=%d" a b c (+ a b c)))
	    c)))
  (print (format "a=%d, b=%d, c=%d, total=%d" a b c (+ a b c)))
  (print (+ a b c)))

"a=1, b=2, c=4, total=7"

"a=10, b=20, c=40, total=70"

"a=100, b=200, c=400, total=700"

"a=1, b=20, c=400, total=421"

421
421

慣れないとごちゃごちゃしているように見えるかもしれないが、この例でlet*の性質が分かる。既に定義濟みのシンボルをオーバーライドすることが出來る。C言語でもブロックで圍むことにより、Lispほどスマートではないが同樣のオーバーライドが可能だ。しかし、性質はそうでも使用目的はたいていの場合、データの局所化はもちろんだが、處理の局所化というニュアンスが强いと思う。

(let* (a b total)
  (setq a (save-excursion
	    (set-buffer (find-file-noselect "0015.el"))
	    (goto-char (point-min))
	    (read (current-buffer))))
  (setq b (string-to-number (read-string "b=")))
  (setq total (+ input-a input-b))
  (format "a=%d, b=%d, total=%d" a b total))

(let* ((input-a (save-excursion
		  (set-buffer (find-file-noselect "0015.el"))
		  (goto-char (point-min))
		  (read (current-buffer))))
       (input-b (string-to-number (read-string "b=")))
       (total (+ input-a input-b)))
  (format "input-a=%d, input-b=%d, total=%d" input-a input-b total))

上の2つは結果は同じだが、私は下の書き方を好む。變數aと變數bの入力處理がバインディングのところに集約されるからだ。このあたりはもっと複雜な機能になった場合にプログラムの見通しという點で重要になると思われる。

| | Comments (0) | TrackBack (0)

August 24, 2014

プログラミングを始めるには(27)

さて、これまで大した說明も無しにいくつかのプログラムを書いてきたが、ここらでまとめて書いておく。ただし、ここに書く說明は私の思い込みというかイメージのようなものであり、正式なものはEmacsのヘルプやInfoを參照してもらいたい。

defun is a special form in `C source code'.
(defun name arglist [docstring] body...)

Define name as a function.
The definition is (lambda arglist [docstring] body...).
See also the function `interactive'.

まず、elispでファンクションを書く場合に必ずと言って良いほどよく使用するのがdefunである。上はEmacsが表示するdefunのヘルプであるが、機能の名前を定義する、もしくは機能としての名前を定義するというふうに訳せる。defundefine functionの造語ではないのか?と思う人も居るだろう。定義するのは機能でなく名前の方?というわけだ。このへんのニュアンスは日本語よりも英語の方が敏感であり、物事の真理を突いているかもしれない。おそらくここでlambdaのことを書けば納得出来るかもしれないが、そうするとここの説明がかなり長くなってしまうし、プログラミングを始めたばかりの人には混乱するだけであろう。

(defun name (args)
  "docstring"
  body)

(fset 'name (function (lambda (args)
			"docstring"
			body)

なので、ここではnameというシンボルのファンクションセルにlambdaリストをバインドするという意味で、機能の名前を定義するというニュアンスになる...ということにしておこう。

defun2番目のパラメータはarglistなっている。普段何の氣なしに(defun funcname (arg)...と書いている(arg)のことである。argではなく(arg)、つまりリストなのだ。C言語の場合は、main(){}と書くと、それはメイン關數の定義になり、mainに續く()の中にパラメータを書ける。しかし、その場合の()lispでいうところのリストを意味するわけではなく、ただそういうシンタックスだというだけだ。これに關してはあまりクドクド書いてもピンと來ないかもしれないが、interactiveとの關連でこの先イヤでもちゃんと理解する必要が出てくる。

3番目のdocstringは、ファンクションの說明を書くところだ。いい加減なことを書くくらいなら省畧しても良い。書くのであれば、他のファンクションのヘルプを參照して、何を書くべきかを考えた方が良い。とくにそのファンクションを他のプログラマに提供する場合は、入出力仕樣くらいは明確にした方が良いだろう。このdocstringに記載した内容は、そのファンクションのヘルプに揭載される。

4番目のbody...はファンクションの處理を記述する部分だ。俗に暗默のprognフォームと呼ばれており、最後に評價される式の結果がそのファンクションの戾り値になることを意識して書く必要がある。

| | Comments (0) | TrackBack (0)

August 23, 2014

プログラミングを始めるには(26)

手っ取り早いプログラミング例として、Emacs上の簡單な編集コマンドを例題とした。これはEmacsの操作に慣れてくれば、それなりに書けるようになるであろう。たとえば、カーソルを下に移動するコマンドは「C-h k」に續けて「C-n」とタイプすると、next-lineのヘルプが表示される。同樣に操作の度にヘルプを參照していれば、ひとまとまりの編集操作をどういうコマンドまたはそれに代わるファンクションを使えば實現出來るか、だいたい分かってくる。

しかし、キーボードマクロで事足りる場合はそれをいちいちプログラムにはしないと思われる。キーボードマクロでは出來ない編集操作、つまり編集する際に都度判斷を要する場合は、プログラムを書く必要がある。前囘やった手順に判斷を入れてみよう。

(1) カーソルをバッファの先頭に移動する。
(2) カーソルを5行下に移動する。
(3) カーソルのある行が「START」を含む場合は、その行をバッファの先頭に移動する。
(4) カーソルのある行が「START」を含まない場合は、その行をバッファの終端に移動する。

(defun edit-new-command-3 ()
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (forward-line 5)
    (let* ((start-p (looking-at ".*START"))
	   (beg (point))
	   (end (progn (forward-line) (point)))
	   (string (buffer-substring-no-properties beg end)))
      (delete-region beg end)
      (if start-p
	  (goto-char (point-min))
	(goto-char (point-max)))
      (insert string))))

判斷を要すると言っても、それほど大きく變わるわけではない。判斷するポイントに來た時に判斷しておいて、あとでその判斷結果を元にその後の處理をすれば良いだけだ。ちなみにプログラムがどう動いているかを見たければ、處理對象のテキストとプログラムを別々のフレームで表示して、edebugを使ってトレースするとテキストのバッファをどうポイントが動き、どうテキストが變更されるかよく分かる。

| | Comments (0) | TrackBack (0)

プログラミングを始めるには(25)

前囘はelispで書いたものをexsedawkで無理矢理?書くとこうなるというのを示してみた。sedawkexEmacsのようなエディタではないので、フィルタースクリプトを書いてそれでファイルを處理し、その出力をファイルに書き込むという方法になる。プログラムを書くということで、それだけでもたいへんなのに、内部でテキストがどうなっているのか見えないsedawkは、簡單な處理にしか使えない氣がする。まあ、sedawkの達人たちはそんなことならどうにでもなると言うのだろうが...。

exでやるのが效率的ならばそれでやれば良いし、それでやるには複雜過ぎるというのなら、elispでやれば良いと思う。手段は選擇出來るのだから。

Screenshot_from_20140822_235635

Screenshot_from_20140822_235645


上の畫像はEmacsviper-modeを使って、exのコマンドを使ってみたところである。viユーザにしてみれば、なんでEmacsからviもどきを使わなければならないか?と言われそうだが、これはviユーザにEmacsを使わせるための機能ではなく、Emacsユーザにviの機能を提供するためのものだ。

たとえばawkに長けた人は、こういったテキスト處理をたいていawkでやろうとするだろう。そして、awkの制限に引っ掛かったり、ちょっと考えただけでawkでは出來そうにないような複雜なことや大規模なことは、別に自分が出來なくても良いと考えるだろう。「それが出來ないからどうだって言うのだ?」と開き直るかもしれない。しかし、せっかくこれからプログラミングを習得しようという時に、そうなることが豫め分かっている手段を選ぶのは得策だろうか?テキスト處理やそれに類することなら何でも出來ると言えた方がかっこ良くないだろうか?と思う。

| | Comments (0) | TrackBack (0)

August 21, 2014

プログラミングを始めるには(24)

前々囘から以下の手順をelispで書くとどうなるかという、プログラムを初めて書こうとしている人が考えそうなネタについて考えている。しかし、これがelispビギナーに興味あるネタかどうかは自信がない。ではこの問題を他の方法でやろうとするとどうなるのかについて考えてみたい。

(1) カーソルをバッファの先頭に移動する。
(2) カーソルを5行下に移動する。
(3) カーソルがある行をkillする。
(4) カーソルをバッファの終端に移動する。
(5) カーソルのある位置にkillした行をyankする。

まず、Emacsの永遠のライバル?vi (ex)でやるとどうなるのか。

#!/bin/sh

ex -e -s <<EOF $1
:6,6 m $
:wq!
EOF

exのプログラムというのはないが、上記のようにサイレントモードで編集手順を書くことは出來る。なんと(1)から(5)の操作をたった1行で出來てしまう。この效率の良さからvi (ex)Emacsよりも優秀なエディタとする議論があるのかもしれない。ただ、Emacsには昔からviのエミュレーションモードがいくつかあり、viのようなコマンドの方が編集效率が良い場合はその操作性をEmacsの中で利用出來るようになっている。

有名なsedでやるとこうなる。

#!/bin/sh

TEMPFILE=`mktemp`

cat > $TEMPFILE <<EOF
1,5p
7,\$p
6h
g
\$p
1,\$d
EOF

sed -f $TEMPFILE $1

rm -f $TEMPFILE

(1) パターンスペースの1行目から5行目までプリントする。
(2) パターンスペースの7行目から最終行までプリントする。
(3) パターンスペースの6行目をホールドスペースにコピーする。
(4) ホールドスペースをパターンスペースにコピーする。
(5) パターンスペースの最終行をプリントする。
(6) パターンスペース全體を削除する。

sedは全然分からないのでアレコレ試して、適當な名前のスクリプトファイルに上記のように書いてやっと目的の結果になった。しかし、なぜこういう手順で良いのかサッパリ分からないので說明出來ない。たぶん慣れた人ならもっとシンプルかつ效率的に書くのだろう。

#!/bin/awk -f

NR >= 1  {
    line[NR] = $0;
}

END {
    for (i = 1; i <= 5; i++) {
	print line[i];
    }
    for (i = 7; i <= NR; i++) {
	print line[i];
    }
    print line[6];
}

さらに有名なawkでやるとこうなる。sedのクセで惱んだおかげで經驗値が上がったのか、こちらはあまり惱まずに濟んだ。しかし、sedawkも讀み込んだストリームの狀態を想像して書かないといけないので、複雜な條件で複雜なことをやろうとすると、頭が良い人じゃないとキツい氣がする。たぶんperlも同じようなものだろう。單純に1〜5行と7行〜最終行を先にプリントして、最後に6行目をプリントすると書いても、要するに入力ストリームを全行プリントと解釋されてしまうのだ。

| | Comments (0) | TrackBack (0)

August 20, 2014

プログラミングを始めるには(23)

前囘は「こんな關數を書いてはいけません!」という見本のような關數を書いたところで終わった。正確には「elispプログラムの中でこんなファンクションを使ってはいけません」と書くべきかもしれない。では具體的に何をどうすれば良いかを說明するために、正解例を示す。

(defun edit-new-command ()
  "こんな關數なら書いてイイかも"
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (forward-line 5)
    (let* ((beg (point))
	   (end (progn (forward-line) (point)))
	   (string (buffer-substring-no-properties beg end)))
      (delete-region beg end)
      (goto-char (point-max))
      (insert string))))

人によっては「なんじゃこりゃ?!」と思われるかもしれない。これは、ホントはこのように書かなければならないという一例だ。これだとKeyboard Macro Editorで表示した手順と全然違っている。

最初に書いた惡い例では、すべての手順にコマンドファンクション使用している。まずはこれが良くない。さらに「C-h f」でbeginning-of-bufferのヘルプを讀むと、「Don't use this command in Lisp programs!」と書かれている。end-of-bufferも同樣だ。next-lineについては「If you are thinking of using this in a Lisp program, consider using `forward-line' instead.」と書かれている。ひょっとしたら異論がある人も居るかもしれないが、ヘルプに「interactive ~ function」として定義されているファンクションは使わない方が良い。これはelispプログラムの中で使用するのではなく、Emacsの編集操作を目的として定義されているファンクションだからだ。

(defun edit-new-command-2 ()
  "慣れていない人はたぶんこう書く"
  (interactive)
  (goto-char (point-min))
  (forward-line 5)
  (setq start-position (point))
  (forward-line)
  (setq end-position (point))
  (setq string (buffer-substring start-position end-position))
  (delete-region start-position end-position)
  (goto-char (point-max))
  (insert string))

私も最初はそうだったが、慣れていないうちはたぶんこんな書き方になるのだろう。これでも最初のコマンドの羅列よりは良いのだが、ユーザのバッファ内のテキストを編集する場合は、まずユーザの編集セッションを壞してはいけないことに注意する。ここではカーソルを移動しているので、プログラム實行前と後ではカーソルの位置が變わってしまう。最初の惡い例では、beginning-of-lineend-of-lineによりユーザのmark-ringを、kill-lineによりユーザのkill-ringを變更してしまう。良い例ではこれを防ぐためにsave-excursionを使って、現在の編集セッションから脫線し、フォームを拔けるときに編集セッションを復元する。こうすることでこのコマンドを實行した後もユーザは違和感なく編集を繼續できる。この例の場合は1個のバッファの中だけで處理が終わるが、プログラムの中でバッファを切り替える場合は、脫線フォームを使わないと、プログラムが終了するときにたまたま處理對象としていたバッファが表示された狀態で終わることになる。これでは「立つ鳥後を濁さず」の原則に反することになる。

あとはstart-positionとかend-positionとかで、あまり意識せずに?グローバル變數を使ってしまっている。これもユーザが別の處理で同じグローバル變數を使っていた場合に問題になる。なので變數は出來るだけローカルバインディングを使用した方が良い。通常、defunDoc Stringに「こんな關數なら書いてイイかも」とか「慣れていない人はたぶんこう書く」などとは書かない。これはこの記事の中だけの冗談である。本來はそのファンクションの說明をヘルプを參考にして書くべきだ。

| | Comments (0) | TrackBack (0)

August 19, 2014

プログラミングを始めるには(22)

Emacs上で手っ取り早くプログラミングに慣れるためには、まず簡單で人畜無害な編集コマンドを書いてみると良い。既に書いたようにEmacsの編集機能はすべてEmacs上のコマンドとして實裝されている。なので、それを書いてみるのが一番感覺的に分かりやすいということだ。

(1) カーソルをバッファの先頭に移動する。
(2) カーソルを5行下に移動する。
(3) カーソルがある行をkillする。
(4) カーソルをバッファの終端に移動する。
(5) カーソルのある位置にkillした行をyankする。

こんな手順をコマンドにしたらどうなるだろうか...。プログラムを書いたことがない人が、これを書くためにGNU Emacs Lisp Manualを讀んでも、すぐには出來ないと思う。私の經驗では、最初にあれこれ考えた擧句、キーボードマクロをコマンドに置き換えてみたらどうかと考えた。

まず「C-x (」でキーボードマクロの記錄を開始し、上記の手順通り操作する。操作が終わったら「C-x )」で記錄を完了する。

(name-last-kbd-macro 'edit-command)
[escape 60 14 14 14 14 14 11 11 escape 62 25]

(symbol-function 'edit-command)
[escape 60 14 14 14 14 14 11 11 escape 62 25]

キーボードマクロの記錄が終わったら、name-last-kbd-macroコマンドでそれに名前を付ける(ここではedit-commandという名前を付けた)。そうするとedit-commandは文字コードの羅列によって定義されているように見える。しかし、M-x edit-commandで起動すると、記錄したとおりに實行される。

;; Keyboard Macro Editor.  Press C-c C-c to finish; press C-x k RET to cancel.
;; Original keys: ESC < 5*C-n 2*C-k ESC > C-y

Command: edit-command
Key: none

Macro:

ESC
<			;; self-insert-command
5*C-n			;; next-line
2*C-k			;; kill-line
ESC
>			;; self-insert-command
C-y			;; yank

文字コードの羅列ではどういうコマンドなのか分からないので、edit-named-kbd-macroでさらに中身を見てみると上記のように表示される。最初にタイプした「ESC <」は「ESC」と「<」が分離されて「<」は文字として入力されたように表示されているが、これはKeyboard Macro Editorの仕樣かもしれない。

;; Keyboard Macro Editor.  Press C-c C-c to finish; press C-x k RET to cancel.
;; Original keys: ESC < 5*C-n 2*C-k ESC > C-y

Command: edit-command
Key: none

Macro:

ESC <			;; beginning-of-buffer
C-n			;; next-line
C-n			;; next-line
C-n			;; next-line
C-n			;; next-line
C-n			;; next-line
C-k			;; kill-line
C-k			;; kill-line
ESC >			;; end-of-buffer
C-y			;; yank

ホントはこういう表示になることを期待していたのだが...。まあこれでどんなコマンドをどういう順番で實行したかが判明した。あとはこの手順を關數化すればそれで良いことになる。elispのマニュアルを少しでも讀んだことがあれば、關數はdefunで定義するということくらいは分かるはずだ。

(defun edit-new-command ()
  "こんな關數を書いてはいけません!"
  (beginning-of-buffer)
  (next-line)
  (next-line)
  (next-line)
  (next-line)
  (next-line)
  (kill-line)
  (kill-line)
  (end-of-buffer)
  (yank))

こんな見たことないような變な關數が出來上がる。さてこの關數をeval-defunし、さっきみたいにM-x edit-new-commandと起動してみよう。そしてアレ?!ということなる。起動出來ない。じゃあ、M-x eval-expression(edit-new-command)と關數を實行してみよう。すると實行は出來たものの、豫想と違う結果になる。

(defun edit-new-command ()
  "こんな關數を書いてはいけません!"
  (beginning-of-buffer)
  (next-line)
  (next-line)
  (next-line)
  (next-line)
  (next-line)
  (let ((kill-whole-line t))
    (kill-line))
  (end-of-buffer)
  (yank))

一應、こう修正すると想定通りの動作となるが、動く動かないに關わらず、こんな關數を書いてはいけないし、Emacs上の編集プログラムとしてこんな例は探しても出てこないはず。その理由は次囘確認する。

| | Comments (0) | TrackBack (0)

プログラミングを始めるには(21)

「プログラミングを始めるには」というタイトルのわりに、さあこれからプログラミングを始めようという段階では誰も考えないような話が續いた氣がする。おそらく最近の人はプログラムが書けるようになったら、何か畫面が出てきて、それを操作したら、それらしい動きをするものを想像するのだろう。

(require 'cl)
(require 'eieio)

(defclass test-class ()
  ((data :initarg data
	 :initform '(((man woman) . love) ((oldman sea) . anekdoten)))))

(defmethod test-add ((obj test-class) key1 key2 &optional add)
  (setf (slot-value obj data)
	(append (slot-value obj data) (list (cons (list key1 key2) add)))))

(defmethod test-run ((obj test-class) key1 key2 &optional sym)
  (let* ((value (slot-value obj data))
	 (answer (assoc (list key1 key2) value)))
    (if answer (cdr answer)
      (test-add obj key1 key2 sym)
      (test-run obj key1 key2))))
      
(defun my-app (key1 key2 answer)
  (interactive "sKEY1: \nsKEY2: \nsSYM: ")
  (let* ((test-obj (make-instance 'test-class))
	 (answer (test-run test-obj
			   (intern key1) (intern key2) (intern answer)))
	 (buffer (get-buffer-create "●MY-APP-OUTPUT●")))
    (save-excursion
      (set-buffer buffer)
      (goto-char (point-max))
      (insert (format "\n\n%16s : %s\n%16s : %s\n%16s : %s\n"
		      "key1" key1 "key2" key2 "answer" (symbol-name answer)))
      (pop-to-buffer buffer))))

Ws000096


ちょっとくどくなってきたが、先日來のサンプルプログラムにものすごく簡單な操作性をくっつけるとこうなる。my-appを起動するとミニバッファからKEY1KEY2SYMの3個の入力を要求される。3番目の入力はキーのペアが未登錄の時だけ反映される。●MY-APP-OUTPUT●に表示された上から2つはインスタンスに初期登錄されている情報。何氣に未登錄の場合だけtest-runを呼び返しているが、これは別に再歸のつもりでなく、面倒くさいからそうしただけだ。Emacsではこのmy-appのようなプログラムをアプリとは呼ばず、コマンドと呼ぶ。

さて、ここまでのことをササっとやれるようになるには、Emacsあるいはelispの何を學ぶ必要があるのかについて考えていくことにする。

| | Comments (0) | TrackBack (0)

August 18, 2014

プログラミングを始めるには(20)

プログラミングを始めるときに、プログラミング言語がオブジェクト指向プログラミング言語である方が良いか否か、ということについて前3囘くらいであれこれ試してみた。そうやってOOP前後の違いを味見するには、前の方をある程度實踐していないとむずかしいのかもしれない。今囘の試行錯誤でOOPをやるとストレスが溜まるだけのような結果になったが、それはあくまで私が基本的に無知なだけだと思われる。ただ、上っ面だけ嘗めて分かったようなフリをしたくないので、盲信するのでなくちゃんと自分なりに納得したいと思う。

そして何となくわかった!(笑)

(require 'cl)
(require 'eieio)

(defclass test-class ()
  ((data :initarg data
	 :initform '(((man woman) . love) ((oldman sea) . anekdoten)))))

(defmethod test-add ((obj test-class) key1 key2 &optional sym)
  (setf (slot-value obj data)
	(append (slot-value obj data)
		(list (cons (list key1 key2)
			    (or sym (intern (read-string "STRING: "))))))))

(defmethod test-run ((obj test-class) key1 key2 &optional sym)
  (let* ((value (slot-value obj data))
	 (answer (assoc (list key1 key2) value)))
    (if answer (cdr answer)
      (test-add obj key1 key2 sym))))

(setq test-obj (make-instance 'test-class))

(test-run test-obj 'man 'woman)
love

(test-run test-obj 'oldman 'sea)
anekdoten

(test-run test-obj 'mom 'son 'sexy)
(((man woman) . love) ((oldman sea) . anekdoten) ((mom son) . sexy))

(test-run test-obj 'mom 'son)
sexy

test-obj
[object test-class "test-class"
	(((man woman) . love)
	 ((oldman sea) . anekdoten)
	 ((mom son) . sexy))]

最大の勘違いとしては、インスタンスを作る前にどうしてスロットにアクセス出來るんだろう?という點だった。つまりdefmethodについて「Only the _first_ argument may have a type specifier.」とInfoに書かれているのを讀んでいなかったことになる。ちなみに、defmethoddefunと同樣にedebug-defunするとedebugで追いかけることが可能になるようだ。素晴らしい。これで胸の痞えが半分くらい取れた氣がする。

| | Comments (0) | TrackBack (0)

August 17, 2014

プログラミングを始めるには(19)

defstructの次はdefclassを用いて、このところずーっとやっている同じ例題を書いてみたが...結局は期待外れだったようだ。

(require 'cl)

(defclass test-class ()
  ((data :initarg data
	 :reader test-reader
	 :accessor test-writer
	 :initform '(((man woman) . love) ((oldman sea) . anekdoten)))))

(setq test-obj (make-instance 'test-class))

(defun test-add (key1 key2 &optional sym)
  (setf (test-writer test-obj)
	(append
	 (test-reader test-obj)
	 (list (cons
		(list key1 key2)
		(or sym
		    (intern
		     (read-from-minibuffer "STRING: "))))))))

(defun test-run (key1 key2 &optional sym)
  (let* ((data (test-reader test-obj))
	 (answer (assoc (list key1 key2) data)))
    (if answer (cdr answer)
      (test-add key1 key2 sym))))

(test-run 'man 'woman)
love

(test-run 'oldman 'sea)
anekdoten

(test-run 'mon 'son)
(((man woman) . love) ((oldman sea) . anekdoten) ((mon son) . sexy))

これだったらdefstructでも同じじゃないの?という氣がする。アクセサを自動生成する分、defstructの方がマシかも。クラスオプションに:writerでスロットへの書き込みアクセサを指定すると、實行時にエラーになる。Infoには:readerもエラーになるようなことが書かれているが、readerの方は動くようだ。ただしreaderが本當にリードオンリーかどうかまでは確認していない。とりあえずwriterはaccessorと同じにしておけば良いと思うが、そういうところだけは嚴密に考えるのか。

これでデータと機能をカプセル化したことになるのか甚だ疑問が殘る。それとインスタンスがどちらもベクトルというのも氣になる。心殘りはdefmethodの實用的な使い方がどこにもなかったこと。どのような動作をするかを示す簡單な例しかネット上に無かったのが不思議。結局、最初に書いたインチキ臭いカプセル化が一番自分のイメージと一致していたという皮肉な結果に終わったようだ。

(require 'cl)

(defclass test-class ()
  ((data :initarg data
	 :accessor test-data
	 :initform '(((man woman) . love) ((oldman sea) . anekdoten)))
   (keyw :initarg keyw
	 :accessor test-keyw
	 :initform nil)
   (answ :initarg answ
	 :accessor test-answ
	 :initform nil)))

(setf test-obj (make-instance test-class))

(defun test-add (answ)
  (setf (test-data test-obj)
	(append
	 (test-data test-obj)
	 (list (cons
		(test-keyw test-obj)
		(or answ
		    (intern (read-from-minibuffer "STRING: "))))))))

(defun test-run (key1 key2 &optional answ)
  (setf (test-keyw test-obj) (list key1 key2))
  (setf (test-answ test-obj)
	(cdr (assoc (test-keyw test-obj) (test-data test-obj))))
  (or (test-answ test-obj) (test-add answ)))

(test-run 'man 'woman)
love

(test-run 'oldman 'sea)
anekdoten

(test-run 'mom 'son)
(((man woman) . love) ((oldman sea) . anekdoten) ((mom son) . sexy))

(test-run 'mom 'son)
sexy

test-obj
[object test-class "test-class"
	(((man woman) . love)
	 ((oldman sea) . anekdoten)
	 ((mom son) . sexy))
	(mom son) sexy]

どうせならこうしてはどうかと考えたのが上記の書き方。一應再入するスレッドが無い前提。普通ならローカルバインディングにするような變數も、オブジェクトに持たせてしまえという方針である。こうすると處理が終わった後にいろんな中閒データが殘るので、デバグの役にも立ちそうな氣がする。これでこの例でのOOPの檢討は終わりにする。

| | Comments (0) | TrackBack (0)

プログラミングを始めるには(18)

このタイトルで續けるかどうか疑問ではあるが、オブジェクト指向についてもう少し考えてみる。いきなりエイエイオーするには、まだまだ頭の中が整理出來ていないような氣がするからだ。ちなみにMeadow(Emacs-22.3)では、eieioはcedetパッケージの一部としてpackages/lisp/cedet/eieioにあるが、最新のEmacs-24.4.50ではlisp/emacs-lispに混入されている。つまり外部Lispではなく普通に「part of GNU Emacs」になっている。ということはEmacsのlispディレクトリが今後どんどんOOP化していくかもしれず?だとすればOOPに無關心ではいられなくなっていくわけだ

(require 'cl)

(makunbound 'test-object)

(defun tiki-object-search (obj)
  (assq 'data obj))

(defun tiki-object-add (obj key1 key2 &optional sym)
  (setf (cdr obj)
	(append
	 (cdr obj)
	 (list (cons
		(list key1 key2)
		(or sym
		    (intern (read-from-minibuffer "STRING: "))))))))

(defun tiki-object-run (key1 key2 &optional sym)
  (let* ((obj (tiki-object-search test-object))
	 (answer (assoc (list key1 key2) (cadr obj))))
    (if answer (cdr answer)
      (tiki-object-add obj key1 key2 sym))))

(defun tiki-def-method (tag func)
  (let* ((method (assq tag test-object)))
    (if method (setf (cdr method) func)
      (add-to-list 'test-object (cons tag func)))))

(setq test-object
      '((data (((man woman) . love)
	       ((oldman sea) . anekdoten)))
	(method . tiki-def-method)))

(funcall (cdr (assq 'method test-object)) 'search 'tiki-object-search)
(funcall (cdr (assq 'method test-object)) 'add 'tiki-object-add)
(funcall (cdr (assq 'method test-object)) 'run 'tiki-object-run)

(print test-object)
((run . tiki-object-run)
 (add . tiki-object-add)
 (search . tiki-object-search)
 (data (((man woman) . love)
	((oldman sea) . anekdoten)))
 (method . tiki-def-method))

(defun test-object::run (key1 key2 &optional sym)
  (funcall (cdr (assq 'run test-object)) key1 key2 sym))

(test-object::run 'oldman 'sea)
anekdoten

「プログラミングを始めるには(15)」で書いたインチキ臭いプログラムを少し直してみた。この方が少しはマシなカプセル化になっているのではないかと思われる。で、別に最初から仕組んだ流れではないが、次に試すべきはdefstructだろうと思う。

(require 'cl)

(defstruct test-object data add run)

(setf test-instance
      (make-test-object :data '(((man woman) . love)
				((oldman sea) . anekdoten))
			:add nil
			:run nil))
					    
(setf (test-object-add test-instance)
      (function
       (lambda (data key1 key2 &optional sym)
	 (setf (test-object-data test-instance)
	       (append
		data
		(list (cons
		       (list key1 key2)
		       (or sym
			   (intern
			    (read-from-minibuffer "STRING: "))))))))))

(setf (test-object-run test-instance)
      (function
       (lambda (key1 key2 &optional sym)
	 (let* ((data (test-object-data test-instance))
		(answer (assoc (list key1 key2) data)))
	   (if answer (cdr answer)
	     (funcall (test-object-add test-instance) data key1 key2 sym))))))

(funcall (test-object-run test-instance) 'oldman 'sea)
anekdoten

(funcall (test-object-run test-instance) 'mom 'son 'sexy)
(((man woman) . love) ((oldman sea) . anekdoten) ((mom son) . sexy))

このようにかなりすっきりするが、試した限りではedebugで追いかけることが出來ない?ようだ。やはり無名關數ではデバグ出來なさそうである。それと、インスタンスを作ってから出ないとアクセスするデータが決まらないので、メソッドは後からsetfを使ってスロットに設定してやらないといけない。さらに、スロットは動的にオーバーライド出來るが、動的に增やすことは出來ない?のではないか。おそらくこうした問題はdefclassを使ったOOPでは解決されているものと思われる。

ところで、こうして自力でカプセル化もどきの試行錯誤をやっていて、今までの自分のプログラムの書き方を少し變えてみようという氣になった。正直言って、會社で動かしているelispのバッチ處理は贔屓目に見ても綺麗じゃない。畫面を縱いっぱいに引き延ばしても2畫面以上あるような大きな關數を平氣で書いていた。だが、エイエイオーを使うにはまだいくつかハードルがあるような氣がする。

| | Comments (0) | TrackBack (0)

August 16, 2014

プログラミングを始めるには(17)

サマリの續きを。


「プログラミングを始めるには(4)」
Lispならば何も氣にならないリスト處理をC言語で書くとこんなに面倒だという話。實はC言語で開發をしていた頃でも、こんなプログラムは書いたことがなかったので、結構樂しめた氣がする。EmacsもC言語で書かれているが、實際にリストをどのように處理しているかにはあまり興味がない。


「プログラミングを始めるには(5)」
C言語で開發中にEmacsと出會い、以後それに夢中になったという馴れ初めのような話。それまではBorland社のIDEを好んで使っていた。Emacsを使い始めたばかりの頃は、まだLispなんて全く知らなかったので、自分の.emacsですら思い通りにならなかった。その後、Emacs+gcc+gdbというのが定番の開發環境になり、Rmail、MH-e、GNUSを使うようになって、さらにはEmacsを自分でビルドするようにもなった。


「プログラミングを始めるには(6)」
現在もっとも手輕にプログラミングを始める方法はWindowsパソコンならMeadowをインストールすることであるということを書いた。そして、GNU Emacs Lisp Manualの最初に書かれているのは「Hello World!」ではなく、tとnilという眞僞値の話である。


「プログラミングを始めるには(7)」
Meadowをインストールすると非常に詳細な日本語マニュアルが付いてくるという話。いわゆるGNU Emacs ManualとGNU Emacs Lisp Manualの邦譯版であるが、私はどちらも通讀した記憶は無い。Bindを束縛と譯すのはあまり好きではなく、そういうことが妙に氣になって讀み込む氣になれない。その點、18版の邦譯センスは謎めいた譯注が隨所にあったりして結構良かった氣がする。その他、眞僞値の話の續き。


「プログラミングを始めるには(8)」
プログラミングを始めるにはそれが何よりも面白くなければならないという話。システム開發で實施するプログラミングは實に面白くない。カードパンチ世代はプログラマ、リーダー、マネージャーのいずれも早く引退して慾しい。そして、最低スキル者に合わせた開發方法というのもやめてもらいたいものだ。面倒臭くてしょうがない。ついでに書くと、カードパンチ世代はプログラムの說明書を重視する變な傾向がある。それが揃ってさえいれば安心であるかのように。でも、そういったプログラム仕樣書と單體テストの整合性(仕樣書通りにテストされているかどうか)までは氣にしない。プログラマはソースに書き込むコメントの寄せ集めを讀みたいとは思わないのだ。


「プログラミングを始めるには(9)」
プログラミングを面白いと感じるには實際に自分でやってみて、それを實感しなければならないという話。話題に合っているかどうかはおいといて、最近のほとんどのパソコンに搭載されているのが64ビットCPUであるが、64ビットの最大値っていくつだろうかという素朴な疑問。流石に64ビット版のWindowsにはそれを表示出來る電卓が付屬している。しかし、それ以上の大きな數値を扱うとなるとダメみたいだ。Lispではもともと非常に大きな自然數を扱うことが出來る。bignumとかfixnumと呼ばれているものだ。この記事では2の8192乘という讀めないような數値を表示している。


「プログラミングを始めるには(10)」
Lispでプログラムを書く方法として、大きめの機能をいくつかの關數に分けて書く方法と、まとまった手順を1個のLisp式にしてしまう方法の2つがあるという話。後者は、シェルのコマンドラインで出力を確認しながら、どんどんパイプでつないでいくやり方に似ている。しかし、不用意にグローバル變數を作り散らかさないよう、Lisp式を書く場合はたいていletフォームを使用する。式をどの程度ネストさせるか、それをプログラムの可讀性の基準にしたがる人が居るが、私は反對だ。Lispはその人の思考に任せて書いた方が良い。


「プログラミングを始めるには(11)」
言語仕樣書や言語書籍に讀んで面白い本はあんまりないという話。讀み手に面白いものを提供しようとして書いているわけではないのだから當然だが、面白くないことによってその言語への興味が薄らぐのだとしたら問題だと思う。他に「Lispでエラトステネスのふるい」という神戸大學のページを例に、お決まりの再歸とループの話を。まあ、Emacs上では深度が知れているもの以外あまり再歸で書かない方が良いと思われる。


「プログラミングを始めるには(12)」
現時點の最新Emacsバージョンである24.4.50のビルドについて。23のアルファバージョンの頃はよくCygwinでビルドしていたが、その結果ノートパソコンを1臺壞してしまった。ペン3-1Ghzのパソコンで數時閒もかかるビルドを頻繁にやってはいけない。最近のパソコンなら、假想マシン上のLinuxでも數分でビルドできる。おそらくsavannahサーバからチェックアウトにかかる時閒の方が長い。それと24.4からはewwというブラウザ機能が加わるらしい。


「プログラミングを始めるには(13)」
プログラミングを始めるにはデバグが容易でなければならないという話。プログラムを作ったは良いが、思ったように動かない原因を見つけるのに必要なのがデバガであるが、Emacsにはedebugという非常にシンプルで强力なデバガがある。最初はgnu.emacs.sourcesに投稿されていたものを使っていたが、知らないうちにEmacsにバンドルされていた。唯一注意すべきなのは、巨大なリストの變數の内容を表示しないように操作する必要があるということくらい。これを怠るとたまにEmacsが固まってしまう。


「プログラミングを始めるには(14)」
Emacs外部のコマンドなどの出力を取り込んで處理する方法があるということを書いた。取り込んだ後Emacs上で整形するつもりが、その必要がなくなってしまう場合もある。まあでも、たまにUNIXコマンドで簡單に出來ることをelispで書いてみるのも面白いものではある。


「プログラミングを始めるには(15)」
オブジェクト指向の話。これについては今後もいろいろと書くかもしれないが、實は今elispでOOPすることについていろいろと調査中だ。エイエイオーという面白い名前のパッケージ?があるようで、この記事で試したことを書きなおしてみたいと思っている。

| | Comments (0) | TrackBack (0)

August 15, 2014

プログラミングを始めるには(16)

「プログラミングを始めるには」というタイトルで每度同じようなことを書いていたら記事が15個になってしまった。頭の惡い私にはそろそろわけが分からなくなり始めているので、ここらでサマリを作って話を整理しておこうと思う。


「プログラミングを始めるには」
會社の同僚をプログラミングに引き込もうとしているが、なかなかその氣になる人は居ない。たぶん、プログラムに用が無いのだろう。あと、プログラムを書けるようになるほどにプログラミングを習得するのはかなりハードルが高そうだ、というイメージがあるのかもしれない、というようなことをボヤーっと考えていて、この記事を書いた氣がする。何か計算したり、集計したりする場合は誰もがExcelを使う。几帳面な日本人はとにかく表にして整理するのが大好きだ。聞いた話によれば、WindowsのスタートアップにExcelのショートカットを入れている人も居るらしい。でも、ちょっと複雜なことをやろうとするとVBAが必要だ。私はSEなら開發スキルが必要だとは思わないが、テキスト處理の素養くらいはあった方が良いと思っている。そうでなければベンダが作ってきたプログラムの規模を測ることが出來ないし、どれだけ試驗項目があれば良いかも分からない。それで品質を管理しろと言われても無理だ。記事の後半では、數字を2個入力して、それを足し算し、結果を記錄して表示するプログラムを紹介している。こんなのは單なる思い付きの例であり、この程度ならプログラミングの經驗が無くても何をしているかくらいは分かるはずだ。これすらも分からないというのなら、その人は思考拒否をしているに違いない。


「プログラミングを始めるには(2)」
プログラムを書けた方が良いと言っても、なんでもかんでもプログラムを書いてやれば良いというものではなく、まずはシェルのコマンドで、次にシェルスクリプトで、それでもむずかしければC言語でプログラムを書く、というのが昔から言われている、計算機上の基本的なアプローチである。コマンドでと言われても大したことは出來ないんじゃないかという人が多いかもしれないが、それはWindowsのコマンドプロンプトの話であって、UNIX上のシェルなら、かなりすごいことが出來る。ただし、シェルのコマンドラインですごいことをするにはシェルスクリプトがスラスラ書けるほどのスキルが必要かもしれない。これはEmacsにおいても同樣のことが言える。Emacsをエディタとして使用する際にする操作はすべてEmacs上のコマンドで實現されているのだ。Emacsの操作で出來ないことはEmacs Lispでプログラムを書いて實行することが出來る。つまり計算機上の基本アプローチにかなった操作になっているというわけだ。


「プログラミングを始めるには(3)」
かの有名なエドワード・ヨードンさんは本の中で、2つ以上のプログラミング言語を習得しているプログラマはいろいろな面でスキルが高いと書いている。最初のところで、業務上知り得たプログラミング言語以外に何か習得するのならそれはEmacs Lispだと强引に書き、續いてLispならスイスイ出來るリスト處理をちょっとでもC言語で書こうとすると大變だ、というインチキ臭い例を紹介している。C言語など、他の言語でもリスト處理は出來るし、最近はやりの言語ならたいていリスト處理ライブラリくらいは持っている。だとしたら、Lispなんて要らないじゃんと思われるかもしれないが、それはとんでもなく大きな閒違いだ。リード表現とプリント表現の一致はC言語では出來ないが、Lispでは主要な效能の一つである。型宣言を必要とする言語では土臺無理な話だが、それがプログラミングをする上でどれほど大きなメリットなのかは、實際にLispと他の言語で書き比べてみないと分からないだろう。


サマリと言いつつ全部おさらい出來ていないし、こんなに書いていてはさらに混亂しそうだ(笑)

| | Comments (0) | TrackBack (0)

August 13, 2014

プログラミングを始めるには(15)

あたかも、プログラミングを始めるにはそれがオブジェクト指向でなければならない...ということであるかのように、みなさんはオブジェクト指向が大好きのようだ。それは、これまでに一度もプログラムを書いたことがないような禿げ頭のオッサンでさえ、本屋にそういう本がたくさん竝んでいるというだけでそのように盲信している。一體何がそんなに良いのか?

それは何となく、電氣屋でパソコンを買ったらWindowsがインストールされていて、だからそれを使っているというのと大差ない氣がする。ソフトショップへCコンパイラを買いに行ったら、VISUAL C++が賣っていたからそれを買ってきた、それが何か?というのと同樣だ。前にも書いたが、ピカピカの1年生が最初に學ぶプログラミング言語は、最初に關わった開發プロジェクトが採用するプログラミング言語と決まっているのであり、學生時代にどれほど高尙なソフトウェア工學を學んできても、そんなことはどうでも良いのだ。それに失望したら、もう會社を變わるしかない。一度そのプログラミング言語で開發したという經歷がつくと、以後その呪縛から逃れられなくなる。

オブジェクト指向に關する書籍や文獻は20年以上前からいろいろ見て來た。しかし、これは面白いなと思ったのはほとんど無い。およそ實用的でないサンプルばかりでリアリティが無いのだ。書いている人は著名な人ばかりなんだろうけど、きっとこの人たちはシステム開發なんてやったことないんじゃないだろうかと思えて來る。繰り返しになるが、普通のIT技術者がプログラマで入れられるのはせいぜい10年くらいで、その後はお拂い箱になるか、小賢しい開發管理や營業をやるしかない。その短い閒にソフト開發がどういう經緯でC言語プログラミングからオブジェクト指向プログラミングになっていったのか、そういうことを身をもって體驗出來ている幸運な人はほとんど居ないだろう。だとすれば、なるほど思える書籍、文獻が少ないのにも頷けるのではないだろうか。

しかし、延々と嫌味を言っていてもしょうがないので...何となくオブジェクト指向っぽい書き方を考えてみる。

;; This buffer is for notes you don't want to save, and for Lisp evaluation.
;; If you want to create a file, visit that file with C-x C-f,
;; then enter the text in that file's own buffer.

(setq test-object nil)

(add-to-list 'test-object
	     '(test-data
	       ((man woman) . love)
	       ((oldman sea) . anekdoten)))

(add-to-list
 'test-object
 '(run (lambda (a b &optional sym)
	 (let ((answer (assoc (list a b)
			      (cdr (assoc 'test-data test-object)))))
	   (if answer (cdr answer)
	     (setf (cdr (assoc 'test-data test-object))
		   (append
		    (cdr (assoc 'test-data test-object))
		    (list (cons
			   (list a b)
			   (or sym
			       (intern
				(read-from-minibuffer "STRING: "))))))))))))

(funcall (cadr (assoc 'run test-object)) 'man 'woman)
love

(funcall (cadr (assoc 'run test-object)) 'oldman 'sea)
anekdoten

(funcall (cadr (assoc 'run test-object)) 'mom 'son 'sexy)
(((man woman) . love) ((oldman sea) . anekdoten) ((mom son) . sexy))

もしも、オブジェクト指向がこういったことを見榮え良くラッピングするものだとしたら、こんな面倒臭いことはアホらしくてやってられないのだが、どうなんだろう。誰かに否定してほしいものだ(笑)

(setq test-object nil)

(add-to-list 'test-object
	     '(data
	       ((man woman) . love)
	       ((oldman sea) . anekdoten)))

(add-to-list
 'test-object
 '(search (lambda () (assoc 'data test-object))))

(add-to-list
 'test-object
 '(add (lambda (objdata a b &optional sym)
	 (setf (cdr objdata)
	       (append
		(cdr objdata)
		(list (cons
		       (list a b)
		       (or sym
			   (intern
			    (read-from-minibuffer "STRING: "))))))))))

(add-to-list
 'test-object
 '(run (lambda (a b &optional sym)
	 (let* ((objdata (funcall (cadr (assoc 'search test-object))))
		(answer (assoc (list a b) (cdr objdata))))
	   (if answer (cdr answer)
	     (funcall (cadr (assoc 'add test-object)) objdata a b sym))))))

(funcall (cadr (assoc 'run test-object)) 'man 'woman)
love

(funcall (cadr (assoc 'run test-object)) 'oldman 'sea)
anekdoten

(funcall (cadr (assoc 'run test-object)) 'mom 'son 'sexy)
(((man woman) . love) ((oldman sea) . anekdoten) ((mom son) . sexy))

大差ないが、ちょっと書き換えてみた。でも、こんなんで大きめの機能を書ける氣がしないし、この書き方だとedebugを使ったデバグが出來ないし、traceも見れないんじゃないだろうか。賴りになるのはエラーが出たときの*Backtrace*だけになる。なので、こんな大したことない機能でも書くのが大變だし、閒違えると原因を見付けるのがむずかしい。

もともと、オブジェクトにデータ型を尋ねることが出來るんだから多重定義なんて必要ないし、いちいちデータと機能を1個のオブジェクトに入れるといっても面倒なだけでメリットが無いような氣がする。それに、ググってもロクな情報がないということはlisperはオブジェクト指向に大した期待をしていないような氣がするのだが...。

| | Comments (0) | TrackBack (0)

August 09, 2014

プログラミングを始めるには(14)

以前の記事で「手っ取り早くプログラミングを始めるには普段使用しているPCにMeadowをインストールすることである」というようなことを書いた。これは普通の人が使用しているPCのほとんどがWindows PCであろうと想定してのことであって、プログラミングをする環境としてはやはりUNIXの方が優れている。

Windowsの前身?であるMS-DOSもバージョン2.0までは、これをプログラミング環境と呼んでよいかどうかは分からないが、MASM(マクロアセンブラ)が標準で入っており、ファイル管理はUNIXにかなり似ていた。Windowsのコマンドプロンプトが馴染む人ならUNIXにあまり違和感は無いはず。しかもUNIXのシェルはコマンドプロンプトのコマンドインタプリタよりも格段に便利で使いやすい。

UNIXといっても、今はほとんどの人にとってUNIX=Linuxであろうと思われる。純粹にUNIXというとfreeBSDはそれに當たるのかもしれない。しかし、freeBSDをたとえば最近のCentOS7やUbuntu14あたりの操作性までもっていくのはかなり骨が折れる作業だ。なにしろインストール直後は端末ログインであり、X11の環境設定をちゃんとしてやらないとデスクトップ環境として使えないというシロモノだからだ。私も最近freeBSD10に挑戰してみたが、端末ログインしたあとにstartxして、X11の畫面まで立ち上がるようにしたところでギブアップした。こういうのを整備していくのが好きな、根っからのシステム管理者指向の人には良いのかもしれないが、それをやっているだけで疲れてしまって、本來の目的であるプログラミングをする以前に挫折してしまう。

それに比べると、最新版のUbuntu(Debian Linuxの派生ディストリビューション)、CentOS(RedHat Enterprise Linuxのクローン)、Fedora(RedHat Enterprise Linuxの實驗版)の3つはインストールも非常に分かりやすいし、VMware Playerのような假想マシンで動かしてもWindows環境とほとんど變わらないくらいの域に達していると思われる。なので、この文章はFedora上のEmacsで書いている。いよいよWindowsのお尻に火が付くのかもしれない。Windows8のような評判の惡いものをリリースしている場合ではないし、CALで使用端末數分さらに金を取るなんてことをやっている場合ではないのだ。

で、私の場合、Linux上で文章を書けるようにするには、むかしEgg+Wnnからの入力で癖になった「ラ行L打ち」の設定をしなければならないのであるが...WindowsのMS-IMEだと簡單に出來るのに、Linuxだと簡單ではないし、同じIBUSでもディストリビューションによってAnthyだったりkkcだったりして、結構鬱陶しいのだ。一應設定變更は出來たが、日本語入力中に英字を打とうとするとF10の半角變換でもうまくいかない。少なくとも先頭が英大文字だったら自動的に直接入力になって慾しいものだが、これは贅澤というものだろうか?

ちなみに、ローマ字設定を變えたい人は/usr/share配下にある日本語入力エンジンのディレクトリをくまなく探して、自分でハッキングしてください。こんなもの、ディストリビューションのバージョンが變わると設定ファイルも變わるようなので、ここに書いておいても無駄だと思うし、書かれている通りにやったら壞れたというアホな人も居そうなので割愛する(笑)

さて、餘談が長くなり過ぎた。UNIX上でEmacsを使えば、UNIX上のコマンドや他のスクリプトで書いたプログラムの出力を取り込んで處理するということが容易になる。

たとえば、Emacs Lisp Introというドキュメントではファイル中の文字數、語句數、行數を表示するプログラムをelispで書くための說明が書かれているが、それをすべてelispで書くのではなく、數え上げの部分はUNIX上のwcコマンドに任せて、その結果を處理するように書けば手閒が省けるわけだ。

Screenshot_from_20140809_182921


(require 'cl)

(defun my-wc-command (dir)
  (interactive "DDirectory: ")
  (let ((default-directory dir))
    (with-temp-buffer
      (shell-command "wc *" t)
      (let ((line-list (mapcar
			(function
			 (lambda (line)
			   (delete "" (split-string line "[ 	]+"))))
			(split-string (buffer-substring-no-properties
				       (point-min)
				       (1- (point-max)))
				      "\n")))
	    (buffer (generate-new-buffer "*WC COMMAND*")))
	(save-excursion
	  (set-buffer buffer)
	  (dolist (line line-list)
	    (insert (nth 3 line) ","
		    (nth 0 line) ","
		    (nth 1 line) ","
		    (nth 2 line) "\n")))
	(pop-to-buffer buffer)))))

非常に安直に作るとこうなる。shell-commandの出力をいじっているのは、ファイル名を左端に表示するためである。これではあまりにも安直過ぎて、指定したディレクトリにさらにディレクトリがあると結果出力のバッファに文字列をinsertしているところでランタイムエラーになる。wcコマンドが「それはディレクトリです」というエラーを出すため、期待している出力にならないからだ。

それならばということで、さらに安直にwcコマンドのエラー出力をNULLリダイレクトするとどうなるか、と考えた。するとOKになるようである。今この文章を書いているディレクトリで實行するとOKでも、別の、たとえばホームディレクトリとかで實行すると樣子が變わってくる。そのためwcコマンドが處理すべきなのは「*」だけでなく「.*」も必要なのだと分かった。

(require 'cl)

(defun my-wc-command (dir)
  (interactive "DDirectory: ")
  (let ((default-directory dir))
    (with-temp-buffer
      (shell-command "wc .* * 2>/dev/null" t)
      (let ((line-list (mapcar
			(function
			 (lambda (line)
			   (delete "" (split-string line "[ 	]+"))))
			(split-string (buffer-substring-no-properties
				       (point-min)
				       (1- (point-max)))
				      "\n")))
	    (buffer (generate-new-buffer "*WC COMMAND*")))
	(save-excursion
	  (set-buffer buffer)
	  (dolist (line line-list)
	    (insert (nth 3 line) ","
		    (nth 0 line) ","
		    (nth 1 line) ","
		    (nth 2 line) "\n")))
	(pop-to-buffer buffer)))))

修正版はこれ。ほとんど何も變わっていなくて、wcコマンドに渡すパラメータに「.*」を加えて、標準エラー出力をNULLリダイレクトしただけである。つまりUNIXコマンドをどう打つかによって、それを受け取るプログラムが大きく變わるわけだ。

(defun my-wc-command (dir)
  (interactive "DDirectory: ")
  (let ((default-directory dir)
	(buffer (generate-new-buffer "*WC COMMAND*")))
    (save-excursion
      (shell-command (concat "wc .* * 2>/dev/null | "
			     "awk '{OFS=\",\";print $4,$1,$2,$3}'") buffer)
      (pop-to-buffer buffer))))

出來るだけUNIXコマンド側に處理をさせるとこうなる。これだと別にシェルから實行すれば良くて、何のためにelispで書くのか分からなくなる。まあ、これは例が惡いのだろう。

| | Comments (0) | TrackBack (0)

August 03, 2014

プログラミングを始めるには(13)

プログラミングを始めるにはデバグが容易でなければならない。

プログラムは書けるようになればそれで良いというものではない。自分または他人が期待する動作をしなければ意味がない。そのためには期待通りの動作をしているかどうかを確認する必要があるが、それはプログラミング言語や動作環境に大きく左右される。デバグが難しい、あるいは面倒な場合、バグの摘出はどんどん後工程に持ち越され、後になればなるほど解析を困難にする。

プログラマの性格に左右されるとは思うが、動くかどうかは置いといて、さっさと設計書通りのコーディングを終わらせたいという人から、書くからには設計書を鵜呑みにせず、場合によってはインタフェースだけを念頭に置いてじっくりコーディングし、動作するかどうか自信がない部分は動かしながら進める人まで、樣々であると思われる。後者に近いほど結果的な(絕對的な)品質は良くなるが、品質管理ではそんなふうにコーディングする人のことは考慮しないで品質計畫を立てるために、プロセス的な品質管理においては困ったことになる。

個人的には、假に自分で書いた設計であってもその通りにコーディングしたことは無いし、初めて使うライブラリを呼び出す場合は、簡單なドライバを書いて必ず動作確認したものだ。なので、コーディングが終わるとともにフラグ取りも、單體試驗もほとんど終わっている。ただし、勘違いしていた場合は悲慘なことになるだろう。

こういうやり方は、自分の端末でエディタを使ってコーディングし、自分の端末でコンパイルし、實行形式のファイルまで作成出來る環境でなければ不可能だ。コーディングペーパーにプログラムを書き、それをカードにパンチして計算機に讀み込ませていた時代に、上記のようなやり方をしていた人が居たとは思えない。そもそもプログラムの走行やデバグは實機でなければ出來ないので、自分に割り當てられたマシンタイム以外にやりたくても出來なかったのだ。しかも、ちょっとでもバグがあれば再開處理が動いて、バグ解析が困難極まりない。

當時に比べればソフトウェア工學もずいぶん進步したと思うが、今でもスクラッチ開發では當時の文化が色濃く殘っているし、その當時を經驗している人は、單體試驗や結合試驗でバグが出ないプログラムの品質を良いと評價することは絕對にない。そしてそういう人たちの指導を受けて育つ若いプログラマは、バグが出ないと怒られるので、ウソのバグ票を書いたりする。そんなことをしていて品質の良いプログラムが出來るわけがない。

いずれにしても、書いたプログラムがちゃんと動作するかどうか確認出來る環境がなければ話にならないし、確認するのが面倒では問題を見つけられない。

Screenshot_from_20140803_11


C言語のプログラムをgdbで走行するとこんな感じになる。Missing separate...という變なメッセージが出ているが氣にしない。ここまでに、0002.cを-gオプション付きでコンパイルし、0002という實行ファイルを作成する、Emacsの中でgdbを起動(M-x gdb)し、main關數の先頭にブレイクポイントを設定(b main)し、runしたところである。Emacsの中で實行しているとはいえ、ちゃんと實行している行と、ブレイクポイントがある行を表示している。

Screenshot_from_20140803_135301


Emacsを端末ウインドウ(Gnome端末)で動かすとこんな感じになり、ブレイクポイントや實行行はキャラクタ表示される。GNUスタイルのインデントは「=>」を表示するためかと勘繰りたくなるところだが、むかしからGNUのコーディングスタイルはC言語でもLispでもインデントが2カラムだった。ステップ實行していき、print文を實行するとinput/outputというバッファが現れて、端末への表示狀況を確認出來る。こんなのはむかしはなく、print文の表示はgdbのコマンドバッファに表示されていた。それに端末ウインドウでもちゃんとキーワードがハイライトされていて、このへんにはさすがに時の流れを感じさせられる。

Ws000093


C言語のプログラムをgdbで走行する場合、Emacsは外部のgdbプロセスと通信する形で行なうが、Emacsの中でemacs-lispのデバグ走行を行う場合は、Emacsの中だけで完結する。先日のエラトステネスのふるいを走行する場合、M-x edebug-all-defs、M-x eval-bufferを實行すれば準備完了で、あとは實行したい關數を起動すれば良い。すると、あらかじめブレイクポイントを設定しておかなくても、最初に實行する關數の先頭でストップする。さらにEmacsはリカーシブ編集モードになり、モード名のところには「*Debugging*」と追加表示される。

このあとは、スペースキー(edebug-step-mode)でどんどんステップ實行していき、關數呼び出しのところに來るとその關數に實行行が移る。ここではバッファ中のすべての關數がedebugの對象になっているが、もしも走行する必要のない關數が混じっている場合は、個々にedebug-defunすれば良い。edebug中に「t」(edebug-trace-mode)または「T」(edebug-Trace-fast-mode)をタイプすると、どんどん自動的にステップ實行する。「T」は高速トレースモードだ。「h」とタイプすると現在カーソルがあるところまで實行して停止する。

しかしこのようなデバグ方法の場合は、やはり再歸呼び出しが無い方が良いと思われる。たとえばrange關數では、關數の處理結果が一番深い再歸に達した後で(10)、(9 10)、(8 9 10)、と順番に連續表示されるが、ループで書き直したrange2という關數なら實行中にtoという變數を常に參照可能だ。

Ws000094


ループで書いた處理をトレースするとこんな感じになる。關數呼び出しのパラメータと戾り値が分かるわけだ。上手く關數分けをして書けば、これだけでも十分なデバグ情報になる。

Ws000095


最後にちょっと變わった例を。餘程變わった人でないとこういう書き方はしないと思うが、この2つのうちSchemerは上のような書き方を好む?らしい。しかし、こういう場合でもedebug-eval-top-level-formを使えば追いかけることが可能だ。下の例は關數名を使わない書き方に直しただけで、最初からこんなふうに書く人はほとんどいない。

| | Comments (0) | TrackBack (0)

August 02, 2014

プログラミングを始めるには(12)

ちょっと脫線して最新Emacsの話を。

先日來、これで通算何囘目か忘れたが、またFedora20やCentOS7を假想マシンにインストールしていた。まあ最近はPCの高性能化に伴い假想マシンの性能もどんどんアップしていて、それらのディストリビューションをフルインストールするのにだいたい30分くらいで出來てしまう。ときには夜のうちにインストールDVDのISOイメージをダウンロードしておいて、翌朝の朝食後から出社するまでにインストールが完了してしまうというお手輕なものになりつつある。

そしてLinuxインストール後にまずやるのが、Emacsの開發trunkからソースをチェックアウトしてのビルド作業。これが出來ればある程度ライブラリ系パッケージが揃うということで習慣になっている。

現在のEmacsバージョンは、公表されている安定バージョンが24.3で、開發trunkは24.4.50である。FedoraもCentOSもインストール時に開發用ワークステーションを選んでフルインストールしておけば良いが、それを怠ると最初のautogen.shから、いきなりautoconfやautomakeのバージョンが古いとか、configureではX11のライブラリが無いとか、makeinfoが無いとか、とにかくyum searchとyum installのオンパレードになってしまう。

Screenshot_from_20140802_080633


で、上の畫面の「GNU/Linux」というリンクをクリックするとewwというWEBブラウザ?が起動する。いままでEmacs上のブラウザというとW3かemacs-w3mだと思っていたが、どうやら24.4でリリースされる新機能らしい。

Screenshot_from_20140801_233351


これにはちょっと驚いたし、開發者たちはEmacsで今後何を實現していくのだろうと、樂しみにもなってきた。既にWidgetとかはあるが、Emacs上で見榮えの良い實用アプリケーションが出來たら良いのになあとはいつも思っている。

| | Comments (0) | TrackBack (0)

« July 2014 | Main | September 2014 »