R语言经典问答:深入领略.Internal函数
写R语言的书,许多城市提到一个汗青话题,就是“Scheme和R语言的干系”。也有许多人问过我,R毕竟那边受了Scheme可能Lisp影响。这个提问也正好提供了一个很好的例子。
提问原帖:Understanding how .Internal C functions are handled in R
“黑邪术”
本日咱们从set.seed()函数的源代码(如下所示)聊起。
可以明明看到提前界说的宏(checkArity、CADR、CADDR等)和函数的四个参数(call、op、args和env),卖力正利用的时候,set.seed()貌似只有一个参数,好比set.seed(1)。
那这些“黑邪术”毕竟是做什么的。
SEXP attribute_hidden do_setseed (SEXP call, SEXP op, SEXP args, SEXP env)
{
SEXP skind, nkind;
int seed;
checkArity(op, args);
if(!isNull(CAR(args))) {
seed = asInteger(CAR(args));
if (seed == NA_INTEGER)
error(_("supplied seed is not a valid integer"));
} else seed = TimeToSeed();
skind = CADR(args);
nkind = CADDR(args);
GetRNGkind(R_NilValue); /* pull RNG_kind, N01_kind from
.Random.seed if present */
if (!isNull(skind)) RNGkind((RNGtype) asInteger(skind));
if (!isNull(nkind)) Norm_kind((N01type) asInteger(nkind));
RNG_Init(RNG_kind, (Int32) seed); /* zaps BM history */
PutRNGstate();
return R_NilValue;
}
checkArity和op
先说checkArity,checkArity通过挪用Rf_checkArityCall来查抄函数的参数个数是否和预设的相符。
那预设又在哪儿呢??
是打开R源代码的时候了,在文件/src/main/names.c的开头,有这样一个段注释。这里只截取了很小的一段,更完整的各人照旧打开源代码本身看吧。
留意个中几列的名字:printname是函数名,就是我们在R里直接利用的函数名;c-entry,这个是挪用的C函数名,offset是个指
针,就是谁人op参数,由.Internal和.Primitive挪用的函数,每个都有的一个offset;arity,这个就是
checkArity来查抄的参数个数。
/* Table of .Internal(.) and .Primitive(.) R functions
* ===== ========= ==========
* Each entry is a line with
*
* printname c-entry offset eval arity pp-kind precedence rightassoc
* --------- ------- ------ ---- ----- ------- ---------- ----------
{"colSums", do_colsum, 0, 11, 4, {PP_FUNCALL, PREC_FN, 0}},
{"colMeans", do_colsum, 1, 11, 4, {PP_FUNCALL, PREC_FN, 0}},
{"rowSums", do_colsum, 2, 11, 4, {PP_FUNCALL, PREC_FN, 0}},
{"rowMeans", do_colsum, 3, 11, 4, {PP_FUNCALL, PREC_FN, 0}},
但这好像照旧很奇怪,好比rowSums,纵然查辅佐文档,参数个数也不外三个,但它对应的arity是4。这个问题照旧要看源代码办理。
> rowSums
function (x, na.rm = FALSE, dims = 1L)
{
if (is.data.frame(x))
x <- as.matrix(x)
if (!is.array(x) || length(dn <- dim(x)) < 2L)
stop("'x' must be an array of at least two dimensions")
if (dims < 1L || dims > length(dn) - 1L)
stop("invalid 'dims'")
p <- prod(dn[-(1L:dims)])
dn <- dn[1L:dims]
z <- if (is.complex(x))
.Internal(rowSums(Re(x), prod(dn), p, na.rm)) + (0+1i) *
.Internal(rowSums(Im(x), prod(dn), p, na.rm))
else .Internal(rowSums(x, prod(dn), p, na.rm))
if (length(dn) > 1L) {
dim(z) <- dn
dimnames(z) <- dimnames(x)[1L:dims]
}
else names(z) <- dimnames(x)[[1L]]
z
}
撤除前面的参数处理惩罚,真正轮到.Internal挪用rowSums时,简直是4个参数。
.Internal(rowSums(Re(x), prod(dn), p, na.rm))
call、args和env
这三个从名字上也较量容易猜出来是做什么的
args是“真正”的参数,就是我们在利用set.seed()时,赋给函数的参数
call是完整挪用,留着被match.call()捕捉
env是函数挪用情况。
CAR和CDR
其实这俩才是真正的“黑邪术”,也正是R受到Lisp影响的重要浮现。
陈腐的pairlist
R语言里有一种很陈腐并且鲜为人知的数据范例,Pairlist。
说陈腐,是因为pairlist担任自Lisp,可以一直追溯到IBM
704电脑,Lisp降生的时代;说鲜为人知,是因为正常写R剧本,能遇到这玩意的几率太低了。险些只有.Options还能遇到,但用.Options
的人也没几个呀。CAR和CDR就是操纵这种上古数据布局的。
Lisp的CAR和CDR
CAR = Contents of the Address part of Register number
CDR = Contents of the Decrement part of Register number
我感受正凡人应该很难领略毕竟是什么意思,会用Lisp的大神请自觉封锁欣赏器。
#p#分页标题#e#
这么说吧,cons是Lisp各类方言里最根基的函数,和C++里的结构器的观念雷同,用于结构pairlist,好比( cons x y
)。而pairlist又是Lisp里最根基的数据布局,可以搭建更巨大的对象,好比一个链表就可以这么来(cons 42 (cons 69
(cons 613 nil))),具体内容请磨练维基百科。
car和cdr就是对cons单位的最根基操纵,好比( car ( cons x y ) )就是取cons单位的第一个元素x,( cdr ( cons x y ) )就是取第二个元素y。
R底层的代码常常呈现的CAR和CDR就是对Lisp的保存,R受Lisp影响的重要浮现。
CAD4R和CARR
但仅仅保存CAR和CDR好像还不能浮现R受Lisp影响之深,Writing R Extension里用到的CAD4R才是精华呀。
在Lisp里,(cadr ‘(1 2 3))和(car (cdr ‘(1 2 3)))是等价的,雷同地(caar ‘((1 2) (3 4)))和(car (car ‘((1 2) (3 4))))是等价的。
所以在R内里CAAR(x)和CAR(CAR(x))是等价的,CADDR(x)和CAR(CDR(CDR(x))是等价的,谁人CAD4R就是CADDDDR,一共有4个CDR操纵。
假如这都不敷够让你以为R深受Lisp影响,那我也就真的没辙了。
本文转载自:http://thirdwing.github.io/2013/11/24/internal/