Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2009-05-31

ループ変数のふるまいと破壊的変更

| 00:38 | ループ変数のふるまいと破壊的変更 - わだばLisperになる を含むブックマーク はてなブックマーク - ループ変数のふるまいと破壊的変更 - わだばLisperになる

kozimaさんの loop の気持ち悪い使い方 - kozima の日記 - cadr groupを読んで確かにここで挙げられているようなパターンは避けてるかもしれないなー、と思いました。

どれもコードを書いている時の意図を裏切るようなパターンですね。

最初の例は、

(loop for i from 1 to 10 collect (lambda (x) (+ x i)) into funs
    collect i into nums
    finally (return (mapcar #'funcall funs nums)))
;=> (12 13 14 15 16 17 18 19 20 21)

ですが、多分

(loop for i from 1 to 10 collect (let ((i i)) (lambda (x) (+ x i))) into funs
    collect i into nums
    finally (return (mapcar #'funcall funs nums)))
;=> (2 4 6 8 10 12 14 16 18 20)

のような動きを意図しているんだと思います。

dotimesや、dolistでも同じですが、これらは、処理系依存ということなので、

(let (funs nums)
  (dotimes (i 10)
    (push (lambda (x) (+ i x)) funs)
    (push i nums))
  (nreverse (mapcar #'funcall funs nums)))
;=> (10 11 12 13 14 15 16 17 18 19)
; もしくは
;=> (0 2 4 6 8 10 12 14 16 18)

こういう場合は、

(let (funs nums)
  (dotimes (i 10)
    (let ((i i))
      (push (lambda (x) (+ i x)) funs)
      (push i nums)))
  (nreverse (mapcar #'funcall funs nums)))
;=> (0 2 4 6 8 10 12 14 16 18)

のように明示的に束縛をつくってあげる必要があるようです。

loopも同じかなと思って調べたのですが、どうもみあたらないのでloopでは束縛を作らないんじゃないかなと思いました。(詳しい情報希望!!)

他もこれに類する感じで

(loop for i from 1 to 10 collect (incf i))
;=> (2 4 6 8 10)

(loop for i from 1 to 10 collect (let ((i i)) (incf i)))
;=> (2 3 4 5 6 7 8 9 10 11)
(loop for x on (list 1 2 3 4 5 6 7 8) collect (pop x))
;=> (1 3 5 7)

(loop for x on (list 1 2 3 4 5 6 7 8) collect (let ((x x)) (pop x)))
;=> (1 2 3 4 5 6 7 8)

という風に新しい束縛を作ってあげて回避することになります。

対策として、

(defmacro let-new ((&rest vars) &body body)
  `(let ,(mapcar #'list vars vars)
     ,@body))

のようなマクロを定義してみたら役に立つかもと思いましたが、微妙な感じです。

新しい束縛を作っても、

(loop for x on (list 1 2 3 4 5 6 7 8) collect (let-new (x) (pop (cdr x))))
;=> (2 4 6 8)

こういうのは守り切れないので(´▽`*)…

どうもポインタ操作系と、代入操作系に魔が潜んでいる気がしました(*'-')

変更不可能データ & 束縛 ならこういうこともないのでしょう。