Hatena::ブログ(Diary)

わからん

2010.09.03

[] 意図しない変数補足と対策

意図しない変数補足と対策について LOL(OnLisp) に沿ってまとめます。


nif (numeric if) をどう定義するかを例にします。nif は、条件部が負の数、0、正の数のどれかに応じた処理をするマクロです。以下は期待する挙動です。

(nif 0 "positive" "zero" "negative")
;;=> "zero"
(nif 3 "positive" "zero" "negative")
;;=> "positive"
(nif -3 "positive" "zero" "negative")
;;=> "negative"

次のような定義で問題ないように見えます。

(defmacro nif-buggy (expr pos zero neg)
  `(let ((obscure-name ,expr))
     (cond ((plusp obscure-name) ,pos)
           ((zerop obscure-name) ,zero)
           (t ,neg))))

(nif-buggy 0 "positive" "zero" "negative")
;;=> "zero"
(nif-buggy 3 "positive" "zero" "negative")
;;=> "positive"
(nif-buggy -3 "positive" "zero" "negative")
;;=> "negative"

しかし下の例では意図しない変数補足が行なわれてしまいます。

(let ((obscure-name "positive"))
  (nif-buggy 0 obscure-name "zero" "negative"))
;;=> "zero"

(let ((obscure-name "positive"))
  (nif-buggy 3 obscure-name "zero" "negative"))
;;=> 3   これは "positive" と返して欲しかった!

(let ((obscure-name "positive"))
  (nif-buggy -3 obscure-name "zero" "negative"))
;;=> "negative"

解決策の一つは gensym で他とかぶらない名前を作ることです。

(defmacro nif (expr pos zero neg)
  (let ((g (gensym)))
    `(let ((,g ,expr))
       (cond ((plusp ,g) ,pos)
             ((zerop ,g) ,zero)
             (t ,neg)))))

(let ((obscure-name "positive"))
  (nif -3 obscure-name "zero" "negative"))
;;=> "negative"

(let ((obscure-name "positive"))
  (nif 3 obscure-name "zero" "negative"))
;;=> "positive"  期待通り!

(let ((obscure-name "positive"))
  (nif -3 obscure-name "zero" "negative"))
;;=> "negative"

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。

Google