whileでループを回すのは苦手です。
elispを読んでいるときに、whileがある箇所に辿り着くと頭が「うー」ってなります。何か読み辛いんですよ。
- 単純なループだったらdolist
- 構造的なループだったらloopマクロ
を使って欲しいななんて思ったりします。
loopマクロと言っても、全部の機能を使う必要なんかないです。ちょっと便利なループ構文とでも思って使えばそれだけで便利ですよ。
そんなわけで、ちょっとした布教も兼ねて、どんな感じのループの時にどのように書いて欲しいかということを書いてみたいと思います。
その前に準備
リスト作成するのが面倒なのでrange関数を定義しておきます。
(require 'cl) (defun range (minv maxv) (loop for i from minv below maxv collect i)) (range 0 10) ; => (0 1 2 3 4 5 6 7 8 9)
rangeは(range i j)でi...jまでのリストを返す関数です。
単純なloop
リストの中の要素を順次printするような単純なループです。whileループでもかけるんですけど、car,cdrが出てきてさらにsetqが出てきて「今のxsは何?」という感じになります。
(setq xs (range 0 5)) (let ((xs xs)) (while xs (print (car xs)) (setq xs (cdr xs))))
こういうときは、dolistを使って欲しいところです。
(setq xs (range 0 5)) (dolist (x xs) (print x))
すごくさっぱりしてわかりやすいですね。
構造的なループ
alistに対応する引数を入れて、それをループさせるような構造的なループがありますね。そんなのもwhileで書くとわけが分からなくなります。
はじめはwhile。
(setq num-to-kanji-alist '(("0". "零")("1" . "一") ("2" . "二") ("3" . "三")("4" . "四") ("5" . "五")("6" . "六")("7" . "七")("8" . "八")("9" . "九"))) (defun num-to-kanji (beg end) (interactive "r") (save-excursion (let ((xs num-to-kanji-alist)) (while xs (goto-char beg) (let* ((x (car xs)) (pat (car x)) (rep (cdr x))) (while (re-search-forward pat end t 1) (replace-match rep)) (setq xs (cdr xs))))))) ;; 西暦2024 -> 平成?年 ;; 西暦二零二四 -> 平成?年
dolistを使った方が良いですね。
(defun num-to-kanji (beg end) (interactive "r") (save-excursion (dolist (x num-to-kanji-alist) (goto-char beg) (let ((pat (car x)) (rep (cdr x))) (while (re-search-forward pat end t 1) (replace-match rep))))))
xsが消えて読みやすくなりました。でもまだcar,cdrは残ってます。読みづらいですね。「xって何者?」という感じです。
こんな時loopマクロが使われているとうれしいです。読みやすくなるので
(defun num-to-kanji (beg end) (interactive "r") (save-excursion (loop for (pat . rep) in num-to-kanji-alist do (progn (goto-char beg) (while (re-search-forward pat end t 1) (replace-match rep))))))
今をときめく言語の分割代入の機能をloopマクロも持っているんですよ。なので一時的な変数を使う必要がなくなります。
xとかcar,cdrがなくなってますね。すべての変数が意味を持った名前になってます。よみやすい。