メモ:Rのプリミティブ関数のprototype

たまたまissueにこんなのを見かけたのでメモ

github.com

prototypes

prototypeというのが何かというと、

Prototypes are available for the primitive functions and operators, and these are used for printing, args and package checking (e.g. by tools::checkS3methods and by package codetools). There are two environments in the base package (and namespace), ‘.GenericArgsEnv’ for those primitives which are internal S3 generics, and ‘.ArgsEnv’ for the rest.
(https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Prototypes-for-primitives)

ということです(…あんまり理解できてない)。分かりやすいところでは関数をprint()するときに使われているらしく、prototypeが存在するやつは

`+`
#> function (e1, e2)  .Primitive("+")

のように関数の引数が表示されますが、存在しないやつは

`[[`
#> .Primitive("[[")

というように、中身だけ(?)の表示になります。

S3のgeneric functionは.GenericArgsEnv、それ以外は.ArgsEnvに含まれています。

ls(.ArgsEnv)
#>  [1] "%*%"             "as.call"         "attr"           
#>  [4] "attr<-"          "attributes"      "attributes<-"   
#>  [7] "baseenv"         "browser"         "call"           
#> [10] "class"           "class<-"         "emptyenv"       
#> [13] "enc2native"      "enc2utf8"        "environment<-"  
#> [16] "expression"      "forceAndCall"    "gc.time"        
#> [19] "globalenv"       "interactive"     "invisible"      
#> [22] "is.atomic"       "is.call"         "is.character"   
#> [25] "is.complex"      "is.double"       "is.environment" 
#> [28] "is.expression"   "is.function"     "is.integer"     
#> [31] "is.language"     "is.list"         "is.logical"     
#> [34] "is.name"         "is.null"         "is.object"      
#> [37] "is.pairlist"     "is.raw"          "is.recursive"   
#> [40] "is.single"       "is.symbol"       "isS4"           
#> [43] "lazyLoadDBfetch" "list"            "missing"        
#> [46] "nargs"           "nzchar"          "oldClass"       
#> [49] "oldClass<-"      "on.exit"         "pos.to.env"     
#> [52] "proc.time"       "quote"           "retracemem"     
#> [55] "seq_along"       "seq_len"         "standardGeneric"
#> [58] "storage.mode<-"  "substitute"      "switch"         
#> [61] "tracemem"        "unclass"         "untracemem"     
#> [64] "UseMethod"
ls(.GenericArgsEnv)
#>  [1] "-"              "!"              "!="             "%%"            
#>  [5] "%/%"            "&"              "*"              "/"             
#>  [9] "^"              "|"              "+"              "<"             
#> [13] "<="             "=="             ">"              ">="            
#> [17] "abs"            "acos"           "acosh"          "all"           
#> [21] "any"            "anyNA"          "Arg"            "as.character"  
#> [25] "as.complex"     "as.double"      "as.environment" "as.integer"    
#> [29] "as.logical"     "as.numeric"     "as.raw"         "asin"          
#> [33] "asinh"          "atan"           "atanh"          "c"             
#> [37] "ceiling"        "Conj"           "cos"            "cosh"          
#> [41] "cospi"          "cummax"         "cummin"         "cumprod"       
#> [45] "cumsum"         "digamma"        "dim"            "dim<-"         
#> [49] "dimnames"       "dimnames<-"     "exp"            "expm1"         
#> [53] "floor"          "gamma"          "Im"             "is.array"      
#> [57] "is.finite"      "is.infinite"    "is.matrix"      "is.na"         
#> [61] "is.nan"         "is.numeric"     "length"         "length<-"      
#> [65] "levels<-"       "lgamma"         "log"            "log10"         
#> [69] "log1p"          "log2"           "max"            "min"           
#> [73] "Mod"            "names"          "names<-"        "prod"          
#> [77] "range"          "Re"             "rep"            "round"         
#> [81] "seq.int"        "sign"           "signif"         "sin"           
#> [85] "sinh"           "sinpi"          "sqrt"           "sum"           
#> [89] "tan"            "tanh"           "tanpi"          "trigamma"      
#> [93] "trunc"          "xtfrm"

ひとつ見てみるとこんな感じです。

.ArgsEnv[["class"]]
#> function (x) 
#> NULL
#> <bytecode: 0x0000000013d4e5a0>
#> <environment: namespace:base>
is(.ArgsEnv[["class"]])
#> [1] "function"         "OptionalFunction" "PossibleMethod"

これは、関数となっていますが、機能としては表示されているようにNULLを返すだけの退屈なものです。

.ArgsEnv[["class"]](1L)
#> NULL

ほんとうに表示するときに必要な情報が入っているだけのようです。

例外

しかし、上にすでににおわせたように、prototypeはすべてのprimitiveに対して用意されているわけではありません。

The QC function undoc checks that all the functions prototyped in these environments are currently primitive, and that the primitives not included are better thought of as language elements (at the time of writing

$ $<- && ( : @ @<- [ [[ [[<- [<- { || ~ <- <<- =
break for function if next repeat return while

).
(https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Prototypes-for-primitives)

とあるように、「Rの文法要素」とみなされているものには用意されていないような雰囲気です。Rにおいてはすべてが関数なわけですが、こうして特別扱いされているんですね。不思議なことです。続けて、

One could argue about ~, but it is known to the parser and has semantics quite unlike a normal function. And : is documented with different argument names in its two meanings.

とあるので、やはりどこまでが「文法要素」なのかは微妙なところがあるようです。

参考:prototypeを定義している場所

では、具体的に.primitiveのprototypeの定義はいつ入るのかというと、ここです。

github.com

assign("%*%", function(x, y) NULL, envir = .ArgsEnv)
assign(".C", function(.NAME, ..., NAOK = FALSE, DUP = TRUE, PACKAGE,
                      ENCODING) NULL,
       envir = .ArgsEnv)
assign(".Fortran",
       function(.NAME, ..., NAOK = FALSE, DUP = TRUE, PACKAGE, ENCODING) NULL,
       envir = .ArgsEnv)
assign(".Call", function(.NAME, ..., PACKAGE) NULL, envir = .ArgsEnv)
assign(".Call.graphics", function(.NAME, ..., PACKAGE) NULL, envir = .ArgsEnv)
...(略)...

…なんと、地道にひとつひとつ.ArgsEnv/.GenericArgsEnvに代入していっています。なんという力技。。