2010.09.03
■[CL] 意図しない変数補足と対策
意図しない変数補足と対策について 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"