Gauche: apropos はどのようにして変数を見つけるか

公開:2020/10/15
更新:2020/10/15
3 min読了の目安(約3100字TECH技術記事

Gauche には aproposというマクロがあります。

Macro: apropos pattern :optional module

apropos マクロは、引数 pattern にマッチするような定義済みの変数のリストを表示してくれます。
たとえば、名前に bound と付くような関数あるいはマクロが定義されているか調べたいなら、以下のように書きます。

gosh> (apropos 'bound)
:bound?                        (keyword)
:slot-bound?                   (keyword)
class-slot-bound?              (gauche)
global-variable-bound?         (gauche)
hash-bound                     (gauche)
slot-bound-using-accessor?     (gauche)
slot-bound-using-class?        (gauche)
slot-bound?                    (gauche)
slot-unbound                   (gauche)
symbol-bound?                  (gauche)

本記事では Gauche ソースコードを探索して、この apropos マクロがどのような仕組みで実現しているのかを見ていきます。

ソースコードを検索すると、apropos マクロの定義は interactive.scm で見つけることが出来ます。

(define-syntax apropos
  (syntax-rules ()
    [(_ item) (%apropos item (current-module) #f)]
    [(_ item module) (%apropos item module #t)]
    ))

本体は %apropos 関数です。実際のソースはリンク先を参照していただくことにして、ここでは %apropos 関数の中身について簡単なコメントを書きました。


(define (%apropos item module stay-in-module)
  (let ([module    ...] ; 検索対象のモジュール
        [matcher   ...] ; 名前がマッチするかを判定する matcher
        [result    '()] ; マッチした変数を格納
        [searched '()]) ; 検索済みのモジュールを格納

    ;; モジュール mod に対して、マッチする変数を検索
    (define (search mod)
      (unless (memq mod searched)
        (set! searched (cons mod searched))
        (hash-table-for-each
         (module-table mod)
         (^[symbol value]
           (when (matcher (symbol->string symbol))
             (found mod symbol))))))

    (define (visible? sym)
      (global-variable-bound? module sym))
    
    ;; モジュール module にマッチする変数(symbol)が見つかったときに呼び出される
    (define (found module symbol)
      (push! result
             (format #f "~30s (~a~a)~%" symbol 
                     (if (visible? symbol) "" "*")
                     (module-name module))))

    (if stay-in-module ; 検索対象のモジュールを module に限定するか
      (search module)
      ;; 検索対象とするモジュール
      (begin (for-each (^m (for-each search (module-precedence-list m)))
                       (module-imports module))
             (for-each search (module-precedence-list module))))
    ;; 見つかった変数の一覧を表示
    (for-each display (sort result))
    (values)
    ))

search 関数で行っている module-table の呼び出しが実体です。以下に search 関数を再掲します。

    ;; モジュール mod に対して、マッチする変数を検索
    (define (search mod)
      (unless (memq mod searched)
        (set! searched (cons mod searched))
        (hash-table-for-each
         (module-table mod)
         (^[symbol value]
           (when (matcher (symbol->string symbol))
             (found mod symbol))))))

module-table にモジュールオブジェクトを渡すと、ハッシュテーブルが返されます。
そのハッシュテーブルにはシンボルが格納されていて、そのシンボルが検索対象となります。