Wie genau analysiert R `->`, den Operator für die richtige Zuweisung?


76

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

?"->", ?assignOpsUnd 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 getAnywhereund 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"?


2
Tatsächlich ist diese verknüpfte verwandte Frage viel relevanter: stackoverflow.com/questions/23309687/…
MichaelChirico

1
Sie setzen einer Beschriftung einfach einen Wert. Dies bedeutet nicht, dass sie identisch sind
Ole Petersen

Antworten:


71

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:

%token      LEFT_ASSIGN EQ_ASSIGN RIGHT_ASSIGN LBB

In den Zeilen 5140 bis 5150 von gram.c sieht dies dann wie der entsprechende C-Code aus:

Ab Zeile 5044 von gram.c wird schließlich die Definition von install_and_save2:


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, korrekt x -> yals y <- xund nicht zu interpretieren x <- 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_ASSIGNund RIGHT_ASSIGNSymbole 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 xxbinarygegen WRT LEFT_ASSIGN( xxbinary($2,$1,$3)) und RIGHT_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 <-
  • $1ist 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, dh x -> ykombiniert mit meiner früheren Vermutung über <-und ->das Tauschen,

  • $2wird von ->nach übersetzt<-
  • $1 ist x
  • $3 ist y

Aber da das Ergebnis xxbinary($2,$3,$1)statt ist xxbinary($2,$1,$3), ist das Ergebnis immer noch xxbinary(<-, y, x) .


Darauf aufbauend haben wir die Definition von xxbinaryin Zeile 3310 von gram.c :

Leider konnte ich keine richtige Definition finden lang3(oder seine Varianten lang1, lang2etc ...) 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.

1) Ist dies wirklich das einzige Objekt in R, das sich so verhält? (Ich habe an das Zitat von John Chambers aus Hadleys Buch gedacht: "Alles, was existiert, ist ein Objekt. Alles, was passiert, ist ein Funktionsaufruf." Dies liegt eindeutig außerhalb dieser Domäne - gibt es noch etwas Ähnliches?

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 :


2) Wann genau passiert das? Ich habe daran gedacht, dass der Ersatz (3 -> y) den Ausdruck bereits umgedreht hat. Ich konnte aus der Quelle nicht herausfinden, welcher Ersatz das YACC gepingt hätte ...

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 war y <- 3 ; zB ist der Funktion völlig unbekannt, dass Sie eingegeben haben 3 -> y. do_substituteWie 99% der von R verwendeten C-Funktionen werden nur SEXPArgumente verarbeitet - und EXPRSXPim Fall von 3 -> 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 .yDateien), 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 Phase lhs -> rhswird nach rhs <- lhs, **nach ^usw. übersetzt. Dies ist beispielsweise ein Auszug aus einer der Tabellen mit primitiven Funktionen in names.c :

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.


17
In der Zwischenzeit kann ich sagen, dass diese Antwort einen Wert hat gram.y? ok, ich sollte ernsthaft wieder an die Arbeit gehen ...
MichaelChirico

2
Nur für den Datensatz (und auch als vollständiger Parser-Neuling) werde ich bemerken, dass es den Anschein hat, dass es einen Unterschied zwischen dem Typ eines Tokens (hier RIGHT_ASSIGN) und seinem Wert (hier <-, zugewiesen yylvaldurch install_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 durch xxbinarydas erste Argument (dh das $2) weitergegeben wird.
Josh O'Brien

@ Josh O'Brien Vielen Dank für die Eingabe (und die Änderungen auch); an der Oberfläche klingt das für mich vernünftig. Wenn Sie irgendwann dazu bereit sind, können Sie diese oder andere relevante Informationen zu meiner Antwort hinzufügen (ich fürchte, ich würde die Erklärung abschlachten, wenn ich versuchen würde, das selbst zu formulieren).
Nrussell

3
@nrussell Gern geschehen. lang3et 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.
Josh O'Brien

1
Danke für das Update! wie zu **, 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!
MichaelChirico
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.