ファイヤープロジェクト
ブロック
2003-11-12T05:30+09:00   matsu
ブロックは,複数の式を一まとめにして一つの制御単位とするものである.他の多くのブロックと同じ概念であるが,Lispではブロックを表現する複数のオペレータがある.
特殊オペレータprognは一連の式を評価し,最後の式の返り値をprogn式の評価値とする.
> (progn (setf hoge 1)
            (setf hoge (+ hoge 1))
            (setf hoge (* hoge 2))
            (format t "hoge = ~A~%" hoge)
            hoge)
hoge = 4
4
progn式内で複数の式を実行しても最後の式の返り値しかprogn式の返り値とならないので注意する.
特殊オペレータblockは,好きな時に好きな値を返り値として抜けることができるブロックの表現に使用する.まず,blockオペレータにブロックのシンボルを渡し,ブロック内でreturn-fromを使用するとブロックから抜けることができる.return-fromにはブロックとブロックの評価値を指定する.
> (setf a 1)
1
> (block hoge
  (if (equal a 1)
      (return-from hoge 'ret1)
    (format t "a != 1~%"))
  (if (equal a 2)
      (return-from hoge 'ret2)
    (format t "a != 2~%"))
  'ret3)
RET1
> (setf a 2)
2
> (block hoge
  (if (equal a 1)
      (return-from hoge 'ret1)
    (format t "a != 1~%"))
  (if (equal a 2)
      (return-from hoge 'ret2)
    (format t "a != 2~%"))
  'ret3)
a != 1
RET2
> (setf a 3)
3
> (block hoge
  (if (equal a 1)
      (return-from hoge 'ret1)
    (format t "a != 1~%"))
  (if (equal a 2)
      (return-from hoge 'ret2)
    (format t "a != 2~%"))
  'ret3)
a != 1
a != 2
RET3
この例はhogeというブロックを指定し,変数aの値に応じてreturn-fromするポイントとブロックhogeの評価値をかえている.blockの特徴は,関数に似ている.実際defunすると,暗黙的にblockによるブロックが作成され,その中に関数定義がなされている.
> (defun funcb (x)
       (if (equal x 2)
           (return-from funcb 'fuga)
         (return-from funca 'foo)))
> (defun funca (x)
       (if (equal x 1)
           (return-from funca 'hoge)
         (funcb x)))
> (funca '1)
HOGE
> (funca '2)
FUGA
> (funca '3)
*** - RETURN-FROM: no block named FUNCA is currently visible
この例は引数の返り値に応じて返り値や処理を変更している.引数に3を指定した場合のように,funcbがfuncaから呼び出されていてもfuncbでいきなりfuncaから抜けようとしてもダメである.名前がnilのブロックもあり,このブロックから抜けるにはreturnを使用する.名前がnilのブロックは,いくつかのオペレータで暗黙的に使用されている.
> (dolist (i '(1 2 3 4 5))
      (format t "hoge~%")
      (setf i (- i (random 3)))
      (if (<= i 0)
          (return 'hoge)))
hoge
hoge
HOGE
この例は乱数を使用しているので,出力は実行する毎に異なる.
tagbodyはいわゆるgoto文を実現するブロックである.tagbody式内では,ラベルを指定でき,go式によってそのラベルの場所に制御を移すことができる.
> (tagbody
      (setf a (+ 1 (random 3)))
      (if (equal a 1)
          (go hoge1)
        (if (equal a 2)
            (go hoge2)
          (if (equal a 3)
              (go hoge3))))
      hoge1
      (format t "HOGE1~%")
      (go break)
      hoge2
      (format t "HOGE2~%")
      (go break)
      hoge3
      (format t "HOGE3~%")
      (go break)
      break
      (format t "break~%"))
HOGE2
break
NIL
なんかウザいサンプルになった.
letはlet式内でのみ有効な変数を作成し,さらに複数の式をまとめる.レキシカルコンテキストと呼ばれるらしいが,ローカル変数つきブロックと言った方が分かりやすいだろうか.
> (let ((x 'hoge) y)
       (format t "~A~%" x)
       (format t "~A~%" y) 
       x)
HOGE
NIL
HOGE
xのように初期値を設定する場合は変数と初期値を括弧で囲う.yのように初期値を設定しない場合は括弧で囲う必要はない.yのように初期値を設定しない場合はnilが設定される.let式全体の評価値は最後の式(ここではx)の評価値である.さて,こんなものを見せられると,
> (let ((x 'hoge) (y (format nil "~AFUGA" x)))
       (format t "x = ~A~%" x)
       (format t "y = ~A~%" y))

*** - EVAL: variable X has no value
などとしたくなるのが人情だろう.結果は「xに値がない」である.では,事前にxに対してsetfしてみる.
> (setf x 'foo)
FOO
> (let ((x 'hoge) (y (format nil "~AFUGA" x)))
       (format t "x = ~A~%" x)
       (format t "y = ~A~%" y))
x = HOGE
y = FOOFUGA
NIL
> x
FOO
すなわちletでローカル変数を定義(と言ってよいのかな?)する際に,つまりこの例ではyの初期値を「xの値」+"FUGA"とする際には他のローカル変数を参照できない,ということになる.それをしたい場合には,letをネストする.
> (let ((x 'hoge))
       (let ((y (format nil "~AFUGA" x)))
         (format t "x = ~A~%" x)
         (format t "y = ~A~%" y)))
x = HOGE
y = HOGEFUGA
NIL
このようなletのネストが嫌な場合はlet*というのを使用すると,見ためがすっきりするかもしれない.以下の例は上のletのネストと意味的には同じである.
> (let* ((x 'hoge) (y (format nil "~AFUGA" x)))
       (format t "x = ~A~%" x)
       (format t "y = ~A~%" y))
x = HOGE
y = HOGEFUGA
NIL
ブロックからちょっと焦点がずれるが,letのローカル変数定義をより柔軟にしたdestructing-bind(※)について記述する.これはイメージとしてはPerlの
my ($hoge $fuga $foo) = @_;
が近いかもしれない.使ってみる.
> (destructuring-bind (a (b c)) '(hoge (fuga foo))
       (format t "~A~%" a)
       (format t "~A~%" b)
       (format t "~A~%" c))
HOGE
FUGA
FOO
NIL
ポイントは第一引数と第二引数のパタンである.ここで言うパタンとは,リストの要素数である.上の場合,第一引数の要素数と第二引数の要素数が一致しなければならない.第一引数と第二引数の要素数が異なる下の例ではいけない.
;; 第二引数のリストの要素数が第一引数のリストの要素数より少ない
> (destructuring-bind (a (b c)) '(hoge fuga foo) 
       (format t "~A~%" a)
       (format t "~A~%" b)
       (format t "~A~%" c))

*** - The object to be destructured should be a list with 2 elements, not (HOGE FUGA FOO).

;; 第二引数のリストの要素数が第一引数のリストの要素数より多い
> (destructuring-bind (a (b c)) '(hoge (fuga foo) bar)
       (format t "~A~%" a)
       (format t "~A~%" b)
       (format t "~A~%" c))

*** - The object to be destructured should be a list with 2 elements, not (HOGE (FUGA FOO) BAR).
ただし,最上位のリストの要素数が一致すれば,その下位のリストの要素数は必ずしも一致しなくてもよいようだ.
;; barは無視される
> (destructuring-bind (a (b c)) '(hoge (fuga foo bar))
       (format t "~A~%" a)
       (format t "~A~%" b)
       (format t "~A~%" c))
HOGE
FUGA
FOO
NIL

;; cにはnilが入る
>  (destructuring-bind (a (b c)) '(hoge (fuga))        
        (format t "~A~%" a)
        (format t "~A~%" b)
        (format t "~A~%" c))
HOGE
FUGA
NIL
NIL
下位のリストは,第二引数の方が多ければ多い分は無視され(上の例で言うと一つ目の式のbar),第一引き数の方が多ければ,前から順番に埋められて第二引数に該当がないものはnilになるようだ(上の例で言うと二つ目の式のc).
※オペレータ名が長いがtab補完があれば問題ない.clispにはそれがある.
matsu(C)
Since 2002
Mail to matsu