LoginSignup
4
4

More than 5 years have passed since last update.

既存関数内で使われている変数を外部から上書きする

Posted at

関数内で使われている変数を一時的に変えたい場合があると思います。
バッドノウハウかもしれませんが、個人用の設定にはとても有用です。

Dynamic scope

(setq foo 1)

(defun bar1 () foo)

(let ((foo 2))
  foo                        ; => 2
  (bar1)                     ; => 2
  )

foo                          ; => 1
(bar1)                       ; => 1

一時的に関数bar内の変数fooがグローバル変数fooではなく、letで定義したfooを見るようになりました。
しかし、これはダイナミックスコープ時のみ有効ですので、レキシカルスコープの場合には以下のようになってしまいます。

Lexical scope

;;; -*- lexical-binding: t -*-

(setq foo 1)

(defun bar1 () foo)

(let ((foo 2))
  foo                        ; => 2
  (bar1)                     ; => 1
  )

foo                          ; => 1
(bar1)                       ; => 1

レキシカルスコープでは関数bar1内の変数fooはグローバル変数fooを見ています。
(1行目に特殊コメントを書いておくとレキシカルスコープになります。)

Override Inner variables safely

;;; -*- lexical-binding: t -*-

(setq foo 1)

(defun bar1 () foo)

(let ((_foo foo))
  (unwind-protect
      (progn
        (setq foo 2)
        foo                   ; => 2
        (bar1)                ; => 2
        )
    (setq foo _foo))
  )

foo                           ; => 1
(bar1)                        ; => 1

上記の方法ならダイナミック/レキシカル両方のスコープでセーフですね。
一時的にグローバル変数fooの値を変更して、最後にunwind-protectによって確実に元の値に戻しています。

Macro

;;; -*- coding: utf-8; lexical-binding: t -*-

(require 'cl-lib)
(defmacro overriding-let (bindings &rest body)
  (declare (indent 1))
  (let (original-pairs
        local-pairs
        overriding-targets
        (bin `,bindings))
    (cl-dotimes (x (length bin))
      (let ((b (nth x bin))
            s v)
        (cl-typecase b
          (cons (setq s (or (ignore-errors (car b)) b))
                (if (boundp s)
                    (progn
                      (setq v (symbol-value s))
                      (setq original-pairs (cons (cons s v) original-pairs))
                      (setq overriding-targets (cons b overriding-targets)))
                  (setq local-pairs (cons b local-pairs))))
          (symbol
           (setq local-pairs (cons b local-pairs)))
          (t (error "Binding format error")))))

    `(let ,local-pairs
       (unwind-protect
           (progn
             (mapc (lambda (x)
                     (let ((s (intern (symbol-name (car x))))
                           (v (car (cdr x))))
                       (set s v)))
                   ',overriding-targets)
             ,@body)
         (mapc (lambda (x)
                 (let ((s (car x))
                       (v (cdr x)))
                   (set s v)))
               ',original-pairs)))))


(setq foo1 1)
(setq foo2 9)

(defun bar1 () foo1)
(defun bar2 () foo2)

(overriding-let ((foo1 2)
                 (foo2 99)
                 (foo3 30)  ;; グローバル変数ではないのでローカル変数として扱う
                 foo4)      ;; グローバル変数ではないのでローカル変数として扱う (宣言のみ)
  (bar1)                       ; => 2
  (bar2)                       ; => 99
  foo1                         ; => 2
  foo2                         ; => 99
  foo3                         ; => 30
  foo4                         ; => nil
  )

(bar1)                         ; => 1
(bar2)                         ; => 9
foo1                           ; => 1
foo2                           ; => 9
(ignore-errors foo3)           ; => nil
(ignore-errors foo4)           ; => nil

マクロ化して設定に書いておくと、面倒な記述を減らしてスマートになりますね。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4