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

きくらげ観察日記

好きなことを、適当に。

Gaucheでロード時定数を作る

Gaucheには元々ロード時定数という機能はありませんが、マクロを使ってうまいこと頑張ると作ることができます。

;; ロード時定数を保存するためのモジュール
(define-module *constant-table*)

;; モジュール module の中で expr を評価し、 *constant-table* に保存
;; この位置にある式を保存された値の読み出しに置き換える。
(define-macro (load-time-constant module expr)
  (define new-var (gensym))
  (with-module *constant-table*
    (eval `(define ,new-var
             (with-module ,module (eval ,expr (current-module))))
          (current-module)))
  `(with-module *constant-table* ,new-var))

このマクロのキモは、頑張って色々な環境を引っ張り回してevalしまくっている所です。
ロード時定数なのでletの中のローカル変数などは見ることができませんが、第一引数に指定したモジュールの環境でexprを評価し、その計算結果を保持することができます。


では、実際にこのマクロがどのように動くか見てみましょう。

(use math.prime)
;; math.prime の *primes* は遅延リストであり、
;; n 番目の要素が必要になった時に値が計算される。

(define (do-something-with-large-prime)
  (let1 p (car (drop *primes* 7000000)) ; ここで *primes* が計算される
    p))

(time (do-something-with-large-prime))
;; => 122949829
;(time (do-something-with-large-prime))
; real   5.190
; user   5.180
; sys    0.020

呼び出し時に*primes*の値を計算しているため、実行時間は5秒とかなり遅いです。
この関数をload-time-constantを使って書きなおしてみましょう。

;; *primes* を初期化し、未計算の遅延リストに戻す
(reset-primes)

;; define中に計算してるので、読み込むのに少し時間がかかる
(define (do-something-with-large-prime2)
  (let1 p (load-time-constant user (car (drop *primes* 7000000)))
    p))

(reset-primes) ; 念の為もう一度リセット
(time (do-something-with-large-prime2))
;; => 122949829
;(time (do-something-with-large-prime2))
; real   0.000
; user   0.000
; sys    0.000

(reset-primes)が呼ばれるたびに*primes*は未評価の遅延リストに戻るので、do-something-with-large-prime2の呼び出し時にpの値を評価しているのであれば、この関数の実行にも5秒程度かかるはずです。
しかし、timeの結果を見ていただければわかりますが、実行時間はほとんどかかっておりません。


ロード時定数など無くても、実際はトップレベルでdefineすれば事足りるのですが、わざわざ外に出すほどでもない定数もあるでしょうし、以前書いた(current-load-path)の問題も、このマクロによってある程度は解決できるはずです。