Das ist also eine triviale Frage, aber es nervt mich, dass ich sie nicht beantworten kann, und vielleicht bringt mir die Antwort einige Details darüber bei, wie R funktioniert.
Der Titel sagt schon alles: Wie funktioniert R parse ->
, die obskure Zuweisung auf der rechten Seite?
Meine üblichen Tricks, um darauf einzugehen, schlugen fehl:
`->`
Fehler: Objekt
->
nicht gefunden
getAnywhere("->")
Es wurde kein benanntes Objekt
->
gefunden
Und wir können es nicht direkt nennen:
`->`(3,x)
Fehler: Funktion konnte nicht gefunden werden
"->"
Aber natürlich funktioniert es:
(3 -> x) #assigns the value 3 to the name x
# [1] 3
Es scheint, dass R weiß, wie man die Argumente einfach umkehrt, aber ich dachte, die obigen Ansätze hätten den Fall sicherlich geknackt:
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)
Vergleichen Sie dies mit dem regulären Zuweisungsoperator:
`<-`
.Primitive("<-")
`<-`(x, 3) #assigns the value 3 to the name x, as expected
?"->"
, ?assignOps
Und die R Sprache Definition alle einfach erwähnen es als die richtige Zuweisungsoperator in geben.
Aber es ist eindeutig etwas Einzigartiges daran, wie ->
es verwendet wird. Es ist keine Funktion / kein Operator (wie die Aufrufe getAnywhere
und direkt zu zeigen `->`
scheinen), also was ist das? Ist es eine Klasse für sich?
Gibt es etwas zu lernen, außer " ->
ist innerhalb der R-Sprache völlig einzigartig in der Art und Weise, wie sie interpretiert und gehandhabt wird; auswendig lernen und weitermachen"?
Antworten:
Lassen Sie mich vorab sagen, dass ich absolut nichts darüber weiß, wie Parser funktionieren. Abgesehen davon, Linie 296 von gram.y folgenden Token definiert Zuordnung in den (YACC?) Parser R Verwendungen zu repräsentieren:
In den Zeilen 5140 bis 5150 von gram.c sieht dies dann wie der entsprechende C-Code aus:
case '-': if (nextchar('>')) { if (nextchar('>')) { yylval = install_and_save2("<<-", "->>"); return RIGHT_ASSIGN; } else { yylval = install_and_save2("<-", "->"); return RIGHT_ASSIGN; } }
Ab Zeile 5044 von gram.c wird schließlich die Definition von
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); }
Also noch einmal, mit null Erfahrung im Umgang mit Parsern, scheint es , dass
->
und->>
übersetzt werden direkt in<-
und<<-
jeweils bei einem sehr niedrigen Niveau im Interpretationsprozess.Sie haben einen sehr guten Punkt angesprochen, als Sie gefragt haben, wie der Parser "weiß", wie er die Argumente umkehren soll
->
- wenn man bedenkt, dass->
dies in der R-Symboltabelle als installiert zu sein scheint<-
- und somit in der Lage ist, korrektx -> y
alsy <- x
und nicht zu interpretierenx <- y
. Das Beste, was ich tun kann, ist, weitere Spekulationen anzustellen, da ich weiterhin auf "Beweise" stoße, um meine Behauptungen zu stützen. Hoffentlich stolpert ein barmherziger YACC-Experte über diese Frage und gibt einen kleinen Einblick. Ich werde jedoch nicht den Atem anhalten.Zurück zu den Zeilen 383 und 384 von gram.y sieht dies nach einer weiteren Analyselogik aus , die sich auf die oben genannten
LEFT_ASSIGN
undRIGHT_ASSIGN
Symbole bezieht :| expr LEFT_ASSIGN expr { $$ = xxbinary($2,$1,$3); setId( $$, @$); } | expr RIGHT_ASSIGN expr { $$ = xxbinary($2,$3,$1); setId( $$, @$); }
Obwohl ich mit dieser verrückten Syntax nicht wirklich Kopf oder Zahl machen kann, habe ich bemerkt, dass das zweite und dritte Argument
xxbinary
gegen WRTLEFT_ASSIGN
(xxbinary($2,$1,$3)
) undRIGHT_ASSIGN
(xxbinary($2,$3,$1)
) ausgetauscht werden .Folgendes stelle ich mir in meinem Kopf vor:
LEFT_ASSIGN
Szenario:y <- x
$2
ist das zweite "Argument" für den Parser im obigen Ausdruck, dh<-
$1
ist die erste; nämlichy
$3
ist der dritte;x
Daher wäre der resultierende (C?) Aufruf
xxbinary(<-, y, x)
.Anwenden dieser Logik auf
RIGHT_ASSIGN
, dhx -> y
kombiniert mit meiner früheren Vermutung über<-
und->
das Tauschen,$2
wird von->
nach übersetzt<-
$1
istx
$3
isty
Aber da das Ergebnis
xxbinary($2,$3,$1)
statt istxxbinary($2,$1,$3)
, ist das Ergebnis immer nochxxbinary(<-, y, x)
.Darauf aufbauend haben wir die Definition von
xxbinary
in Zeile 3310 von 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; }
Leider konnte ich keine richtige Definition finden
lang3
(oder seine Variantenlang1
,lang2
etc ...) in der R - Quellcode, aber ich gehe davon aus, dass es für die Auswertung spezieller Funktionen verwendet wird (dh Symbole) in einer Weise , die mit synchronisiert der Dolmetscher.Updates Ich werde versuchen, einige Ihrer zusätzlichen Fragen in den Kommentaren zu beantworten, so gut ich kann, wenn ich meine (sehr) begrenzten Kenntnisse über den Parsing-Prozess habe.
Erstens stimme ich zu, dass dies außerhalb dieser Domäne liegt. Ich glaube, Chambers 'Zitat betrifft die R-Umgebung, dh Prozesse, die alle nach dieser Analysephase auf niedriger Ebene stattfinden. Ich werde dies jedoch weiter unten etwas näher erläutern. Das einzige andere Beispiel für diese Art von Verhalten, das ich finden konnte, ist der
**
Operator, ein Synonym für den häufigeren Exponentiationsoperator^
. Wie bei der richtigen Zuweisung,**
scheint vom Interpreter nicht als Funktionsaufruf usw. "erkannt" zu werden:R> `->` #Error: object '->' not found R> `**` #Error: object '**' not found
Ich habe dies gefunden, weil es der einzige andere Fall
install_and_save2
ist, in dem der C-Parser verwendet wird :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;
Natürlich spekuliere ich hier immer noch, aber ja, ich denke, wir können davon ausgehen, dass der Ausdruck , wenn Sie ihn
substitute(3 -> y)
aus der Perspektive der Ersatzfunktion aufrufen , immer wary <- 3
; zB ist der Funktion völlig unbekannt, dass Sie eingegeben haben3 -> y
.do_substitute
Wie 99% der von R verwendeten C-Funktionen werden nurSEXP
Argumente verarbeitet - undEXPRSXP
im Fall von3 -> y
(==y <- 3
) glaube ich. Darauf habe ich oben angespielt, als ich zwischen der R-Umgebung und dem Parsing-Prozess unterschieden habe. Ich glaube nicht, dass irgendetwas den Parser speziell dazu veranlasst, aktiv zu werden - sondern alles , was Sie in den Interpreter eingeben, wird analysiert. Ich habe ein wenig getanLesen Sie mehr über den YACC / Bison-Parser- Generator letzte Nacht, und so wie ich es verstehe (auch bekannt als Wette die Farm nicht darauf), verwendet Bison die von Ihnen definierte Grammatik (in den.y
Dateien), um einen Parser in C - zu generieren. dh eine C-Funktion, die das eigentliche Parsen der Eingabe durchführt. Im Gegenzug wird alles, was Sie in eine R-Sitzung eingeben, zuerst von dieser C-Analysefunktion verarbeitet, die dann die entsprechenden Aktionen delegiert, die in der R-Umgebung ausgeführt werden sollen (ich verwende diesen Begriff übrigens sehr locker). In dieser Phaselhs -> rhs
wird nachrhs <- lhs
,**
nach^
usw. übersetzt. Dies ist beispielsweise ein Auszug aus einer der Tabellen mit primitiven Funktionen 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}},
Sie werden feststellen , dass
->
,->>
und**
sind hier nicht definiert. Soweit ich weiß, sind primitive R-Ausdrücke wie<-
und[
usw. die engste Interaktion, die die R-Umgebung jemals mit einem zugrunde liegenden C-Code hat. Ich schlage vor, dass der Parser zu diesem Zeitpunkt (von der Eingabe eines festgelegten Zeichens in den Interpreter bis zur Eingabe eines gültigen R-Ausdrucks durch Drücken der Eingabetaste) bereits seine Magie entfaltet hat, weshalb Sie können keine Funktionsdefinition für->
oder**
durch Umgeben mit Backticks erhalten, wie Sie es normalerweise können.quelle
gram.y
? ok, ich sollte ernsthaft wieder an die Arbeit gehen ...RIGHT_ASSIGN
) und seinem Wert (hier<-
, zugewiesenyylval
durchinstall_and_save2
) gibt. Es scheint mir, dass der Typ verwendet wird, um das Parsen des Ausdrucks zu steuern (uns den Zweig zu senden, der liest{ $$ = xxbinary($2,$3,$1); setId( $$, @$); }
), während sein Wert das ist, was durchxxbinary
das erste Argument (dh das$2
) weitergegeben wird.lang3
et al. sind Inline-Funktionen und finden Sie hier in$RHOME/src/include/Rinlinedfuns.h
. Sieht für mich so aus, als ob ihre Aufgabe hier darin besteht, die einzelnen Token und analysierten Ausdrücke zu listenähnlichen Sprachobjekten zusammenzufassen und so eine vollständig analysierte Version des eingegebenen Ausdrucks aufzubauen.**
, ich kann zumindest das Lesen irgendwann irgendwo vergessen , dass die Betreiber eine Art Vestigal sind, so zumindest habe ich es vor als eine Art einen Außenseiter gesehen anerkannt. Wie auch immer, mein Dienstprogramm ist jetzt voller fragwürdig nützlicher Kenntnisse ... genau wie ich es mag!