読者です 読者をやめる 読者になる 読者になる

レガシーコード生産ガイド

私に教えられることなら

PAIPメモ 1

PAIPでCommon Lispの勉強中に気になったことのメモ。

do

ループ関係をよく知らなかったので詳しく見てみます。

リストの長さをdoを使って調べるlength-doを書いてみます。

(defun length-do (xs)
  (do ((i 0 (1+ i))
       (xs xs (cdr xs)))
      ((null xs) i)))

xsの定義にxsを使ったらダメかな、と思ったけど大丈夫でした。

どんな動きになるのか気になったので、macroexpand-1でdo部分を展開したところ、SBCLでは次のような結果になりました。(処理系依存かはわかりません。)

(BLOCK NIL
  (LET ((I 0) (XS XS))
    (TAGBODY
      (GO #:G1010)
     #:G1009
      (TAGBODY)
      (PSETQ I (1+ I)
             XS (CDR XS))
     #:G1010
      (UNLESS (NULL XS) (GO #:G1009))
      (RETURN-FROM NIL (PROGN I)))))

tagbody初めて見ました。まず変数ですが、let*ではなくletなので、定義中の変数を別の定義で使うことはできなさそうです。試してみます。

(defun length-do (xs)
  (do ((i 0 (1+ i))
       (xs xs (cdr xs))
       (j i (1+ j)))
      ((null xs) (+ i j))))

jの定義に使っているiは無いとwarningが出ました。実行するとunboundエラーになります。

ループはGOを使う原始的なものになっています。なんとなくだけど速そうな気がします。#:G1009の直後の(TAGBODY)は謎です。psetqはペアでsetqできるマクロです。今気づきましたが、CLHS: Macro PSETQを見ると、;pee'set,kyooと発音書いてあるんですね。ピーセットキュー!

それと気になったのが、blockの名前がnilになってることです。gensymした方がいいんじゃないのかなと思いましたが、M.Hiroi's Home Page / xyzzy Lisp Programmingによると

それから block の name に nil を指定した場合、return-from だけではなく return でも脱出することができます。do や while などの繰り返しから return で脱出できるのは、繰り返し処理が block nil の中で定義されているからです。

ということでした。doとdoに展開されるマクロ(そしてそのdoが展開されるblock)ではreturnを使える、と覚えておけそうです。

試してみます。

(defun length-do (xs)
  (do ((i 0 (1+ i))
       (xs xs (cdr xs)))
      ((null xs) i)
    (format t "i: ~A ~%" i)
    (when (> i 1) (return 'YO!))))

(return &optional value)で値を返せるので、iが1より大きければアイサツを返して終了します。

CL-USER>(length-do (list 1 2 3 4))
i: 0 
i: 1 
i: 2 
YO!
広告を非表示にする