メモ:S3のメソッドディスパッチ用に.__S3MethodsTable__.という環境があるっぽい

r-wakalangで、

よくわからないんですけど、S3のメソッドディスパッチの対象になるのは、

  • S3method() で明示的にメソッドとして登録されたもの
  • サーチパス上にある 関数名.クラス() というフォーマットの名前の関数

で、前者は上書きできない、あるいは先に登録されたものが優先される、とか?

と書いたものの、ほんとかな?と思って調べたのでメモ。

ちょっと長いけど、このへん。

    /* This evaluates promises */
    PROTECT(top = topenv(R_NilValue, callrho));
    val = findFunInEnvRange(method, callrho, top);
    if(val != R_UnboundValue) {
    UNPROTECT(1);
    return val;
    }

(https://github.com/wch/r-source/blob/b99f675af6d847e08f2d39fca242c7d39060450c/src/main/objects.c#L260-L266)

ここでまず環境中を探してるっぽい雰囲気。ここで見つかればreturn valしている。 グローバル環境に同名の関数があればそっちがメソッドディスパッチされるのはこういう順序だからなのかな?

    /* We assume here that no one registered a non-function */
    if (!s_S3MethodsTable)
    s_S3MethodsTable = install(".__S3MethodsTable__.");
    SEXP table = findVarInFrame3(defrho, s_S3MethodsTable, TRUE);
    if (TYPEOF(table) == PROMSXP) {
    PROTECT(table);
    table = eval(table, R_BaseEnv);
    UNPROTECT(1);
    }
    if (TYPEOF(table) == ENVSXP) {
    val = findVarInFrame3(table, method, TRUE);
    if (TYPEOF(val) == PROMSXP) {
        PROTECT(val);
        val = eval(val, rho);
        UNPROTECT(1);
    }
    if(val != R_UnboundValue) {
        UNPROTECT(1);
        return val;
    }
    } 

(https://github.com/wch/r-source/blob/b99f675af6d847e08f2d39fca242c7d39060450c/src/main/objects.c#L268-L289)

次に、なんかよくわからないけど.__S3MethodsTable__.というのがあって、そこを先に探すらしい。そこで見つかればreturn valしている。

    if(lookup_baseenv_after_globalenv)
    val = findFunWithBaseEnvAfterGlobalEnv(method, ENCLOS(top));
    else
    val = findFunInEnvRange(method, ENCLOS(top), R_EmptyEnv);
    UNPROTECT(1);


    return val;

(https://github.com/wch/r-source/blob/b99f675af6d847e08f2d39fca242c7d39060450c/src/main/objects.c#L290-L296)

最後に、lookup_baseenv_after_globalenvというグローバル環境と基本環境どっちを先に探すかというオプション?によって違うけどなにやら環境の中を探して回るらしい。

この.__S3MethodsTable__.っていうのはまあ内部的に管理されてるS3メソッドのテーブルっぽいからRからはアクセスできないんだろうな...と思ってたら、ふつうにあった。.BaseNamespaceEnv[[".__S3MethodsTable__."]]でアクセスできるらしい。たとえば、print.Dateはこんな感じ。

get("print.Date", .BaseNamespaceEnv[[".__S3MethodsTable__."]])
#> function (x, max = NULL, ...) 
#> {
#>     if (is.null(max)) 
#>         max <- getOption("max.print", 9999L)
#>     if (max < length(x)) {
#>         print(format(x[seq_len(max)]), max = max, ...)
#>         cat(" [ reached getOption(\"max.print\") -- omitted", 
#>             length(x) - max, "entries ]\n")
#>     }
#>     else print(if (length(x)) 
#>         format(x)
#>     else paste(class(x)[1L], "of length 0"), max = max, ...)
#>     invisible(x)
#> }
#> <bytecode: 0x000000000ea2fff0>
#> <environment: namespace:base>

S3メソッドはここに登録されていくので、exportされてなくてもメソッドディスパッチの対象になる。というからくりらしい。