web-dev-qa-db-ja.com

Rは右代入演算子 `->`をどの程度正確に解析しますか?

ですから、これはちょっとした質問ですが、答えられないのは私を悩ませています。おそらく、答えは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言語内での解釈と処理の方法が完全にユニークであり、記憶して先に進む」以外に、これから学ぶことはありますか?

77
MichaelChirico

パーサーがどのように機能するかについてはまったく何も知らないと言って、この前置きをさせてください。そうは言っても、 gram.yの296行目 は、Rが使用する(YACC?)パーサーでの割り当てを表す次のトークンを定義しています。

%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 -> yy <- xおよび not 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の2番目と3番目の引数がWRTLEFT_ASSIGNxxbinary($2,$1,$3))とRIGHT_ASSIGNxxbinary($2,$3,$1))。

これが私の頭の中で描いているものです:

LEFT_ASSIGNシナリオ:y <- x

  • $2は、上記の式のパーサーに対する2番目の「引数」です。つまり<-
  • $1が最初です。つまり、y
  • $3は3番目です。 x

したがって、結果の(C?)呼び出しはxxbinary(<-, y, x)になります。

このロジックをRIGHT_ASSIGN、つまりx -> yに適用し、<-->が交換されることについての以前の推測と組み合わせて

  • $2->から<-に変換されます
  • $1xです
  • $3yです

ただし、結果はxxbinary($2,$3,$1)ではなくxxbinary($2,$1,$3)であるため、結果は still xxbinary(<-, y, x)になります。


これをもう少し発展させて、gram.cの 行3310にxxbinaryの定義があります

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;
}

残念ながら、Rソースコードでlang3(またはそのバリアントlang1lang2など)の適切な定義を見つけることができませんでしたが、特別な関数(つまりシンボル)を評価するために使用されていると思いますインタプリタと同期されます。


更新解析プロセスに関する私の(非常に)限られた知識を与えることができる限り、コメントであなたの追加の質問のいくつかに対処しようとします。

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にpingを送信した代替手段が何であるかを理解できませんでした...

もちろん、私はまだここで推測していますが、はい、substitute(3 -> y)を呼び出すと、 代替関数 の観点から、式を安全に推測できると思います。常にy <- 3;例えば関数は、3 -> yを入力したことを完全に認識していません。 do_substituteは、Rで使用されるC関数の99%と同様に、SEXP引数のみを処理します。3 -> y(== y <- 3)の場合はEXPRSXPだと思います。これは、R環境と解析プロセスを区別したときに上記でほのめかしたことです。パーサーの動作を具体的にトリガーするものはないと思いますが、インタープリターに入力したすべてすべてが解析されます。私は昨夜少し YACC /バイソンパーサージェネレーターについてもっと読みました、そして私が理解しているように(別名、賭けないでくださいBisonは、(.yファイルで)定義した文法を使用して、Cのパーサーを生成します。つまり、入力の実際の解析を行うC関数です。次に、Rセッションで入力したものはすべて、最初にこのC解析関数によって処理され、次にR環境で実行される適切なアクションが委任されます(ちなみに、この用語は非常に大まかに使用しています)。このフェーズでは、lhs -> rhsrhs <- lhsに、**^に、などに変換されます。たとえば、これは、names.cのプリミティブ関数の テーブルの1つからの抜粋です。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式の実際の評価まで)までに、パーサーはすでにその魔法を働かせているということです。 ->または**の関数定義を、通常のようにバッククォートで囲むことによって取得することはできません。

71
nrussell