所以这是一个微不足道的问题,但是我无法回答这个问题,也许答案会告诉我一些关于R如何工作的更多细节.
标题说明了一切:R如何解析->
,模糊的右侧赋值函数?
我常用的技巧是失败:
`->`
错误:
->
找不到对象
getAnywhere("->")
没有
->
找到任何对象
我们不能直接称它为:
`->`(3,x)
错误:无法找到功能
"->"
但当然,它有效:
(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语言定义都只是简单地提到它作为正确的赋值运算符.
但显然有一些关于如何->
使用的独特之处.它不是一个函数/运算符(作为调用getAnywhere
和直接`->`
表示),那么它是什么?它完全属于自己的一类吗?
除了" ->
R语言在解释和处理方式上是完全独特的,记忆并继续前进" 之外,还有什么需要从中学到的东西吗?
让我先说一下,我对解析器的工作原理一无所知.话虽如此,gram.y的第296行定义了以下标记来表示(YACC?)解析器R使用的赋值:
%token LEFT_ASSIGN EQ_ASSIGN RIGHT_ASSIGN LBB
然后,在gram.c的第5140到5150行,这看起来像对应的C代码:
case '-':
if (nextchar('>')) {
if (nextchar('>')) {
yylval = install_and_save2("<<-", "->>");
return RIGHT_ASSIGN;
}
else {
yylval = install_and_save2("<-", "->");
return RIGHT_ASSIGN;
}
}
最后,从gram.c的第5044行开始,定义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专家会偶然发现这个问题并提供一些见解; 不过,我不会屏住呼吸.
回到gram.y的第383和384行,这看起来像是与上述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
在gram.c的第3310行定义:
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
等...),在R源代码,但是我假设它是用于在与同步的方式评估特殊功能(即符号)口译员.
更新 我将尝试在评论中解决您的一些其他问题,因为我可以给出(非常)有限的解析过程知识.
1)这真的是R中唯一表现得像这样的对象吗?(我记住了通过哈德利的书的约翰·钱伯斯报价:这显然是谎言外域 - 还有什么类似这样的"存在是一个对象,一切都发生的一个函数调用的一切."?
首先,我同意这不属于该领域.我相信钱伯斯的引用涉及R环境,即在这个低级解析阶段之后都会发生的过程.不过,我会在下面再说一点.无论如何,我能找到的这种行为的唯一另一个例子是**
运算符,它是更常见的取幂运算符的同义词^
.与正确的赋值一样,**
似乎不被"识别"为函数调用等...由解释器:
R> `->`
#Error: object '->' not found
R> `**`
#Error: object '**' not found
我发现这是因为它install_and_save2
是C解析器使用的唯一其他情况:
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)什么时候发生这种情况?我记得替补(3 - > y)已经翻了表达; 我无法从消息来源中找出那会替代YACC的替代品......
当然我还在这里猜测,但是,是的,我认为我们可以安全地假设当你打电话时substitute(3 -> y)
,从替代函数的角度来看,表达总是如此 y <- 3
; 例如,该功能完全不知道您输入的内容3 -> y
.do_substitute
就像R使用的99%的C函数一样,只处理SEXP
参数 - EXPRSXP
在3 -> y
(== y <- 3
)的情况下,我相信.当我在R环境和解析过程之间做出区分时,这正是我在上面提到的.我不认为这有什么特异触发解析器春季行动-而是一切您输入到解释被解析.我昨晚做了一些关于YACC/Bison解析器生成器的更多阅读,据我所知(也就是不打赌这个),Bison使用你定义的语法(在.y
文件中)生成一个C中的解析器 - 即一个C函数,它对输入进行实际解析.反过来,在R会话一切你输入首先由该C解析功能,然后委托给在R环境(顺便说一下我使用这个词很随意)采取适当的措施进行处理.在这个阶段,lhs -> rhs
将得到翻译为rhs <- lhs
,**
以^
等..例如,这是从一个摘录的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表达式),解析器已经发挥了它的魔力,这就是为什么正如您通常所做的那样,您无法获得函数定义->
或**
用反引号包围它们.