39

我想知道是否有人可以向我说明 R 如何C从在控制台提示符下键入的 R 命令执行调用。我对R' 对 a) 函数参数和 b) 函数调用本身的处理感到特别困惑。

让我们举个例子,在这种情况下set.seed()。想知道它是如何工作的,我在提示符下输入名称,获取源代码(查看此处了解更多信息),最终看到.Internal(set.seed(seed, i.knd, normal.kind)了一个这导致我....Internals/src/names.cdo_setseedRNG.c

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);
    //...
      //DO RNG here 
    //...
    return R_NilValue;
}
  • 什么是CAR, CADR, CADDR? 我的研究使我相信它们是一个Lisp影响列表的构造,但除此之外,我不明白这些函数的作用或为什么需要它们
  • 做什么checkArity()
  • SEXP args似乎不言自明,但这是函数调用中传递的参数列表吗?
  • 代表什么SEXP op?我认为这意味着运算符(例如在二进制函数中+),但那是什么SEXP call

有没有人能够理解我打字时发生的事情

set.seed(1)

在 R 控制台提示符处,直到定义skind和的点nkind?我发现我无法很好地理解这个级别的源代码以及从解释器到 C 函数的路径。

4

2 回答 2

23

CAR以及CDR如何访问 pairlist 对象,如R Language Definition的第 2.1.11 节所述CAR包含第一个元素,并CDR包含其余元素。编写 R 扩展的第 5.10.2 节给出了一个示例:

#include <R.h>
#include <Rinternals.h>

SEXP convolveE(SEXP args)
{
    int i, j, na, nb, nab;
    double *xa, *xb, *xab;
    SEXP a, b, ab;

    a = PROTECT(coerceVector(CADR(args), REALSXP));
    b = PROTECT(coerceVector(CADDR(args), REALSXP));
    ...
}
/* The macros: */
first = CADR(args);
second = CADDR(args);
third = CADDDR(args);
fourth = CAD4R(args);
/* provide convenient ways to access the first four arguments.
 * More generally we can use the CDR and CAR macros as in: */
args = CDR(args); a = CAR(args);
args = CDR(args); b = CAR(args);

还有一个TAG宏可以访问赋予实际参数的名称。

checkArity确保传递给函数的参数数量是正确的。args是传递给函数的实际参数。op是偏移量指针“用于处理多个 R 函数的 C 函数”(引自src/main/names.c,其中还包含显示每个函数的偏移量和数量的表)。

例如,do_colsum句柄col/rowSumscol/rowMeans.

/* 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}},

请注意,arity在上表中是 4,​​因为(即使rowSumset al 只有 3 个参数)do_colsum有 4,您可以从.Internal调用中看到rowSums

> 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
}
于 2013-10-29T16:47:52.303 回答
20

基本的 C 级配对列表提取函数是CARCDR。(对列表与列表非常相似,但实现为链表并在内部用于参数列表)。它们有简单的 R 等价物:x[[1]]x[-1]. R 还提供了许多两者的组合:

  • CAAR(x) = CAR(CAR(x))这相当于x[[1]][[1]]
  • CADR(x) = CAR(CDR(x))相当于x[-1][[1]], 即x[[2]]
  • CADDR(x) = CAR(CDR(CDR(x))等价于x[-1][-1][[1]],即x[[3]]
  • 等等

访问对列表的第 n 个元素是一个O(n)操作,不像访问列表的第 n 个元素是O(1). 这就是为什么没有更好的函数来访问对列表的第 n 个元素的原因。

内部/原始函数不按名称进行匹配,它们只使用位置匹配,这就是为什么它们可以使用这个简单的系统来提取参数。

接下来,您需要了解 C 函数的参数是什么。我不确定这些记录在哪里,所以我可能对结构并不完全正确,但我应该是一般的部分:

  • call:完整的调用,可能被捕获match.call()

  • op:从 R 调用的 .Internal 函数的索引。这是必需的,因为从 .Internal 函数到 C 函数存在多对一映射。(例如do_summary实现 sum、mean、min、max 和 prod)。该数字是第三个条目names.c- 它始终为 0 do_setseed,因此从未使用过

  • args: 提供给函数的参数对列表。

  • env: 调用函数的环境。

checkArity是一个调用 的宏Rf_checkArityCall,它基本上查找参数的数量(第五列names.c是arity)并确保提供的数字匹配。您必须通过 C 中的很多宏和函数来查看发生了什么 - 拥有可以通过 grep 浏览的 R 源的本地副本非常有帮助。

于 2013-10-29T16:53:23.560 回答