所以这是一个微不足道的问题,但让我烦恼的是我无法回答它,也许答案会教会我更多关于 R 的工作原理的细节。
标题说明了一切:R 如何解析 ->
,晦涩的右侧赋值函数?
我通常的潜入技巧失败了:
`->`
Error: object
->
not found
getAnywhere("->")
no object named
->
was found
我们不能直接调用它:
`->`(3,x)
Error: could not find function
"->"
但当然,它有效:
(3 -> x) #assigns the value 3 to the name x
# [1] 3
看起来 R 知道如何简单地反转参数,但我认为上述方法肯定会破解这个案例:
pryr::ast(3 -> y)
# \- ()
# \- `<- #R interpreter clearly flipped things around
# \- `y # (by the time it gets to `ast`, at least...)
# \- 3 # (note: this is because `substitute(3 -> y)`
# # already returns the reversed version)
将此与常规赋值运算符进行比较:
`<-`
.Primitive("<-")
`<-`(x, 3) #assigns the value 3 to the name x, as expected
?"->"
, ?assignOps
,以及 R Language Definition所有这些都只是作为正确的赋值运算符顺便提及。但是,
->
显然有一些独特之处。使用。它不是函数/运算符(就像对 getAnywhere
的调用和对 `->`
的直接调用似乎证明了这一点),那它是什么?它完全属于自己的一类吗?除了“
->
在 R 语言中它的解释和处理方式是完全独一无二的;记住并继续前进”之外,还有什么可以学习的吗?
最佳答案
让我先说我对解析器的工作原理一无所知。话虽如此,line 296 of gram.y定义以下标记来表示(YACC?)解析器 R 使用中的赋值:
%token LEFT_ASSIGN EQ_ASSIGN RIGHT_ASSIGN LBB
然后,on lines 5140 through 5150 of gram.c ,这看起来像对应的 C 代码:
case '-':
if (nextchar('>')) {
if (nextchar('>')) {
yylval = install_and_save2("<<-", "->>");
return RIGHT_ASSIGN;
}
else {
yylval = install_and_save2("<-", "->");
return RIGHT_ASSIGN;
}
}
最后,从 line 5044 of gram.c 开始,定义
install_and_save2
:/* Get an R symbol, and set different yytext. Used for translation of -> to <-. ->> to <<- */
static SEXP install_and_save2(char * text, char * savetext)
{
strcpy(yytext, savetext);
return install(text);
}
因此,在使用解析器的经验为零的情况下,似乎
->
和 ->>
直接翻译成<-
和 <<-
,分别在解释过程中处于非常低的水平。在询问解析器如何“知道”将参数反转为
->
时,您提出了一个很好的观点。 - 考虑到 ->
似乎作为 <-
安装到 R 符号表中- 从而能够正确解释 x -> y
如 y <- x
而不是 x <- y
.当我继续遇到支持我的主张的“证据”时,我能做的最好的事情就是提供进一步的推测。希望一些仁慈的 YACC 专家会偶然发现这个问题并提供一些见解;不过,我不会对此屏住呼吸。返回 lines 383 and 384 of gram.y ,这看起来像是与上述
LEFT_ASSIGN
相关的更多解析逻辑和 RIGHT_ASSIGN
符号:| expr LEFT_ASSIGN expr { $$ = xxbinary($2,$1,$3); setId( $$, @$); }
| expr RIGHT_ASSIGN expr { $$ = xxbinary($2,$3,$1); setId( $$, @$); }
虽然我无法真正理解这种疯狂的语法,但我确实注意到
xxbinary
的第二个和第三个参数。交换到 WRT LEFT_ASSIGN
( xxbinary($2,$1,$3)
) 和 RIGHT_ASSIGN
( xxbinary($2,$3,$1)
)。这是我在脑海中想象的:
LEFT_ASSIGN
场景:y <- x
$2
是上述表达式中解析器的第二个“参数”,即 <-
$1
是第一个;即 y
$3
是第三个; x
因此,由此产生的 (C?) 调用将是
xxbinary(<-, y, x)
.将此逻辑应用于
RIGHT_ASSIGN
,即 x -> y
,结合我之前关于<-
的猜想和 ->
被交换,$2
翻译自 ->
至 <-
$1
是 x
$3
是 y
但由于结果是
xxbinary($2,$3,$1)
而不是 xxbinary($2,$1,$3)
,结果还是xxbinary(<-, y, x)
.进一步构建这个,我们有
xxbinary
的定义在 line 3310 of gram.c :static SEXP xxbinary(SEXP n1, SEXP n2, SEXP n3)
{
SEXP ans;
if (GenerateCode)
PROTECT(ans = lang3(n1, n2, n3));
else
PROTECT(ans = R_NilValue);
UNPROTECT_PTR(n2);
UNPROTECT_PTR(n3);
return ans;
}
不幸的是,我找不到
lang3
的正确定义。 (或其变体 lang1
、 lang2
等...),但我假设它用于以与解释器同步的方式评估特殊函数(即符号)。更新
鉴于我对解析过程的(非常)有限的了解,我将尽力在评论中解决您的一些其他问题。
1) Is this really the only object in R that behaves like this?? (I've got in mind the John Chambers quote via Hadley's book: "Everything that exists is an object. Everything that happens is a function call." This clearly lies outside that domain -- is there anything else like this?
首先,我同意这不属于该领域。我相信钱伯斯的引述与 R 环境有关,即在此低级解析阶段之后发生的所有过程。不过,我将在下面多谈一点。无论如何,我能找到的这种行为的唯一其他例子是
**
运算符,它是更常见的求幂运算符 ^
的同义词.与正确分配一样,**
解释器似乎没有“识别”为函数调用等......R> `->`
#Error: object '->' not found
R> `**`
#Error: object '**' not found
我发现这个是因为这是唯一的其他情况
install_and_save2
is used by the C parser :case '*':
/* Replace ** by ^. This has been here since 1998, but is
undocumented (at least in the obvious places). It is in
the index of the Blue Book with a reference to p. 431, the
help for 'Deprecated'. S-PLUS 6.2 still allowed this, so
presumably it was for compatibility with S. */
if (nextchar('*')) {
yylval = install_and_save2("^", "**");
return '^';
} else
yylval = install_and_save("*");
return c;
2) When exactly does this happen? I've got in mind that substitute(3 -> y) has already flipped the expression; I couldn't figure out from the source what substitute does that would have pinged the YACC...
当然,我仍然在这里进行推测,但是是的,我认为我们可以放心地假设当您拨打
substitute(3 -> y)
时,从the substitute function的角度来看, 表达式总是 y <- 3
;例如该函数完全不知道您输入了 3 -> y
. do_substitute
,就像 R 使用的 99% 的 C 函数一样,只处理 SEXP
参数 - EXPRSXP
在 3 -> y
的情况下(== y <- 3
),我相信。这就是我在上面对 R 环境和解析过程进行区分时所暗示的。我认为没有什么可以特别触发解析器开始行动 - 而是 一切你输入到解释器被解析。昨晚我做了更多关于 YACC/Bison 解析器生成器的阅读,据我所知(也就是不要在这方面押注农场),Bison 使用您定义的语法(在 .y
文件中)在 C 中生成解析器 - 即执行输入实际解析的 C 函数。反过来,您在 R session 中输入的所有内容首先由这个 C 解析函数处理,然后委托(delegate)在 R 环境中采取适当的操作(顺便说一下,我非常松散地使用这个术语)。在此阶段,lhs -> rhs
将被翻译成 rhs <- lhs
, **
至 ^
,等等...例如,这是从 tables of primitive functions in names.c 之一的摘录:/* Language Related Constructs */
/* Primitives */
{"if", do_if, 0, 200, -1, {PP_IF, PREC_FN, 1}},
{"while", do_while, 0, 100, 2, {PP_WHILE, PREC_FN, 0}},
{"for", do_for, 0, 100, 3, {PP_FOR, PREC_FN, 0}},
{"repeat", do_repeat, 0, 100, 1, {PP_REPEAT, PREC_FN, 0}},
{"break", do_break, CTXT_BREAK, 0, 0, {PP_BREAK, PREC_FN, 0}},
{"next", do_break, CTXT_NEXT, 0, 0, {PP_NEXT, PREC_FN, 0}},
{"return", do_return, 0, 0, -1, {PP_RETURN, PREC_FN, 0}},
{"function", do_function, 0, 0, -1, {PP_FUNCTION,PREC_FN, 0}},
{"<-", do_set, 1, 100, -1, {PP_ASSIGN, PREC_LEFT, 1}},
{"=", do_set, 3, 100, -1, {PP_ASSIGN, PREC_EQ, 1}},
{"<<-", do_set, 2, 100, -1, {PP_ASSIGN2, PREC_LEFT, 1}},
{"{", do_begin, 0, 200, -1, {PP_CURLY, PREC_FN, 0}},
{"(", do_paren, 0, 1, 1, {PP_PAREN, PREC_FN, 0}},
您会注意到
->
, ->>
, 和 **
这里没有定义。据我所知,R 原始表达式如 <-
和 [
等...是 R 环境与任何底层 C 代码最接近的交互。我的建议是,在这个过程中(从你在解释器中输入一组字符并点击“Enter”,直到对有效 R 表达式的实际评估),解析器已经发挥了它的魔力,这就是为什么您无法获得 ->
的函数定义或 **
像往常一样用反引号包围它们。
关于r - R 究竟如何解析 `->` ,右赋值运算符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34599027/