Hatena::Groupcadr

kozima の日記

2010-12-16

map vs. loop

| 00:11

xyzzy の session.l の一部を elisp で書いてみたのですが,そのときに感じたこと。どうも複雑な処理は無名関数にして map するより loop で書いたほうが短いし楽だし読みやすい(気がする)。

ということで具体的にコードを並べてみることにしました。

session.l は,開いていたファイルの情報やバッファのポイント位置,ウィンドウの状態などをファイルに書き出しておいて後で復元する機能を提供しています。その中から,現在開いているファイルの情報を集める部分と,それを復元する部分のコードを比べてみます。

まず xyzzy のソースから。

(defun list-buffer-info ()
  (save-excursion
    (let ((info nil))
      (mapc #'(lambda (buffer)
                (when (file-visited-p buffer)
                  (set-buffer buffer)
                  (push (list (get-buffer-file-name buffer)
                              (point)
                              (cons buffer-mode
                                    (mapcan #'(lambda (mode)
                                                (let ((var (and (consp mode) (car mode))))
                                                  (and (symbolp var)
                                                       (boundp var)
                                                       (symbol-value var)
                                                       (list var))))
                                            *minor-mode-alist*))
                              (mapcar #'(lambda (var)
                                          (and (symbolp var)
                                               (local-variable-p var)
                                               (cons var (symbol-value var))))
                                      *buffer-info-variable-list*))
                        info)))
            (buffer-list :buffer-bar-order t))
      (nreverse info))))

(defun restore-buffer-info (info)
  (let ((obuffer (selected-buffer)))
    (mapc #'(lambda (i)
              (let ((file (pop i)))
                (when (file-exist-p file)
                  (handler-case
                      (let ((point (pop i))
                            (mode (pop i))
                            (minor nil))
                        (when (listp mode)
                          (setq minor (cdr mode))
                          (setq mode (car mode)))
                        (let ((*find-file-auto-mode-function* mode))
                          (find-file file))
                        (goto-char point)
                        (mapc #'(lambda (f) (and (fboundp f) (funcall f))) minor)
                        (mapc #'(lambda (x)
                                  (when (and (car x) (symbolp (car x)))
                                    (make-local-variable (car x))
                                    (set (car x) (cdr x))))
                              (pop i)))
                    (file-error (c)
                      (si:*print-condition c))))))
          info)
    (set-buffer obuffer)))

エラーチェックの部分を削ってますが,基本的に同じことをやる elisp のコード。ついでにちょっとコメント入り。

(defun mylib-session-list-buffer-info ()
  (loop for buffer in (buffer-list)
        as file = (buffer-file-name buffer)
        if file collect
        (with-current-buffer buffer
          (list file
                (point)
                ;; collect minor mode info
                (loop for mode in minor-mode-alist
                      as var = (and (consp mode) (car mode))
                      if (and (symbolp var) (boundp var) (symbol-value var))
                      collect var into vars
                      finally (return (cons major-mode vars)))
                ;; collect local variable info
                (loop for var in mylib-session-buffer-info-variable-list
                      if (and (symbolp var) (local-variable-p var))
                      collect (cons var (symbol-value var)))))))

(defun mylib-session-restore-buffer-info (info)
  (save-excursion
    (loop for (file point (mode . minor) local-vars) in info do
          (if (file-exists-p file)
              (ignore-errors
                ;; open visited file,
                ;; (find-file-noselect file)
                (find-file file)   ; 修正 (2010/12/17)
                (when (and (fboundp mode) (not (eq mode major-mode)))
                  (funcall mode))
                ;; move point to the original place,
                (goto-char point)
                ;; restore minor modes,
                (dolist (f minor) (if (fboundp f) (funcall f)))
                ;; and local variables
                (loop for (var value) in local-vars do
                      (make-local-variable var)
                      (set var value)))
            (message "file `%s' does not exist!" file)
            (sit-for 0.5)))))

map 系は無名関数(特に大きめのもの)と一緒に使うとあまり読みやすくない,ということは以前から思っていましたが,それが顕著に表れた気がします。たぶん,第二引数が離れてしまうせいで「何に対して map するのか」がぱっと見てわかりづらいことが,読みにくさを感じさせる一因になっているのではないかと思います。

可読性ともいくらか関係しますが,コードの行数もやや多くなりがちです。lambda で無名関数を書くと,情報量のわりに場所をとる気がします。特に無名関数がよく使われる map の第一引数のようなところでは,lambda と書いてあってもテンプレート通りという感じで,それほど情報量が多くないのではないでしょうか。そういうところに場所をとるものがあると,余計にコードサイズがかさんでいるように感じられるのかもしれません。

それからもう一つありました。map に限らず,高階関数に無名関数を渡すというスタイルは,インデントが深くなりやすいですね。これは可読性にはそれほど関係しないような気もしますが,書いているときにだんだん右に寄ってくるとなんとなく窮屈に感じます。

2010-11-14

gethash にデフォルト値を渡せるの忘れてた

| 20:57

昨日のエントリ http://cadr.g.hatena.ne.jp/lkozima/20101113/1289661114 を見ていて気付いたけど

(setf #1=(gethash ym h) (1+ (or #1# 0)))

これは

(setf (gethash ym h) (1+ (gethash ym h 0)))

こうしたほうがスマートか。

あるいは

(setf #1=(gethash ym h 0) (1+ #1#))

こう書いても動くけど,これはなんか違う気がする。

youzyouz2010/11/15 00:57(let ((h (make-hash-table)))
(incf (gethash :asdf h 0))
(gethash :asdf h))
こんなんでOKっぽいですね

2010-03-30

loop でうまく書けない件の代替案?

| 21:43

http://cadr.g.hatena.ne.jp/lkozima/20100327/1269692521 の続き。

こうやればきちんと動いてコードの見た目ももう少しそれらしくなる気がして書いてみました。

(loop for x in list and i from 0
  as y = (if (a-test-which-often-returns-false x)
             (a-very-slow-function x)
             dummy)
  unless (eql y dummy)
  collect (compute-the-result i y)
  and do (a-dirty-stuff y))

やっぱりわかりやすくはないかもしれません。

2010-03-27

loop でうまく書けないこと

| 21:22

こんなような loop が書きたいなー,と思うことが時々ありますが書けなくて悲しい。

(loop for x in list and i from 0
  if (a-test-which-often-returns-false x)
  as y = (a-very-slow-function x)
  collect (compute-the-result i y) and do (a-dirty-stuff y))

やりたいことは

  • リストの要素 x とそのインデックス i について
  • たまにしか成立しないある条件を満たすときに
  • 実行に時間のかかる計算をして
  • その結果からさらに計算して得られる値をリストに格納しつつ
  • ついでに最適化のためのごちゃごちゃした処理もする

で,自然に書くと上のようにしたくなるんですけど,as は if の後に書けないので怒られます。

しょうがないので

(loop for x in list and i from 0
  if (a-test-which-often-returns-false x)
  collect (let ((y (a-very-slow-function x)))
	    (a-dirty-stuff y)
	    (compute-the-result i y)))

とか,なんかいまいちだなーと思いながら書くわけです。

2009-12-10

map-and-remove-nils

| 19:28

http://cadr.g.hatena.ne.jp/g000001/20091209/1260369083 より。map しつつ nil じゃないやつだけ集める関数。

こういう関数はときどきほしいです。単純に map してから remove すると、ほとんど nil かつリストが長いときにわりと無駄だなー、という気がするので。

ただ、同じことが loop で書けるので、最近は loop の方に慣れてしまって

(loop for x in list if (test x) collect x)

で済ませてしまいます。

なので最近はあまりほしいと思ってないかも。

loop 使わない方法もあるけど……

ちなみに loop 使わなくても

(mapcan (lambda (x) (if (test x) (list x) nil)) list)

という書き方がありますね。

ただ個人的には、こういう場合にこの書き方をするのは好きじゃありません。というのは、コードの抽象度が問題の要求に比べて不自然に低いと感じるのです。「コード中に書いてあること」と「最終的にやりたいこと」があまり近くありません。なんで lambda の中で明示的に「長さ 1 のリストを」作ったり作らなかったりしなきゃならないんだろうと感じるのです。

LinaLina2011/05/25 14:08Kewl you shulod come up with that. Excellent!

dfhebjdfhebj2011/05/25 20:42eEUL9m <a href="http://usevcmavwdaa.com/">usevcmavwdaa</a>

tbelwsuutbelwsuu2011/05/27 00:55hzEQsA , [url=http://ofuuicxabhkr.com/]ofuuicxabhkr[/url], [link=http://ivlbxadqylrv.com/]ivlbxadqylrv[/link], http://qvpyoewznyxb.com/

nfxilrxlvnfxilrxlv2011/05/28 23:34FFnbPp <a href="http://lydpellplill.com/">lydpellplill</a>

upmunillupmunill2011/06/01 01:08pGvfBs , [url=http://qaewxrtjvdeq.com/]qaewxrtjvdeq[/url], [link=http://fzminelpybvx.com/]fzminelpybvx[/link], http://wnqcddcejnkj.com/