1. ホーム
  2. r

[解決済み] Rは右代入演算子 `->` をどのように解析しているのですか?

2023-05-17 17:03:47

質問

これは些細な質問なのですが、答えられないことが気になります。そしておそらくその答えは、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言語の中で完全にユニークであることです。

どのように解決するのですか?

前置きが長くなりましたが、私はパーサがどのように動作するかについてまったく何も知りません。とは言うものの の 296 行目、gram.y は、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;
    }
  }

最後に の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_ASSIGNRIGHT_ASSIGN のシンボルを使用します。

|   expr LEFT_ASSIGN expr       { $$ = xxbinary($2,$1,$3);  setId( $$, @$); }
|   expr RIGHT_ASSIGN expr      { $$ = xxbinary($2,$3,$1);  setId( $$, @$); }

このおかしな構文を理解することはできませんが、2番目と3番目の引数が xxbinary の第二引数と第三引数が入れ替わり、WRT LEFT_ASSIGN ( xxbinary($2,$1,$3) ) と RIGHT_ASSIGN ( xxbinary($2,$3,$1) ).

頭の中でイメージしているのは、こんな感じです。

LEFT_ASSIGN シナリオ y <- x

  • $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) に変更すると、結果は やはり xxbinary(<-, y, x) .


これをもう少し発展させると、次のような定義があります。 xxbinary の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 , etc...)をRのソースコードに埋め込んでいますが、インタープリタと同期した形で特殊な関数(つまりシンボル)を評価するために使われているのではと推測しています。


更新情報 私は構文解析プロセスに関する私の(非常に)限られた知識を考慮して、コメントであなたの追加の質問のいくつかにできる限り対処しようと思います。

1) このような動作をするRのオブジェクトは本当にこれだけなのでしょうか?(私は ハドレー氏の本から引用したジョン・チェンバースの言葉が頭にあります。 存在するものはすべてオブジェクトである。存在するものはすべてオブジェクトであり、起こることはすべて関数呼び出しである(")。 これは明らかにその領域の外側にあります。 このようなものは他にあるでしょうか?

まず、これがその領域の外側にあることに同意します。Chambers の引用は、R 環境、つまり、この低レベルの解析段階の後に行われるすべての処理に関するものだと思います。しかし、この点については、以下でもう少し触れます。とにかく、この種の動作の他の例として私が見つけたのは ** 演算子です。これはより一般的な指数演算子の同義語である ^ . 右代入と同様に ** はインタープリタによって関数呼び出しなどとして "quot;認識されないようです。

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) 具体的にどのような場合にそうなるのでしょうか?私は、substitute(3 -> y)はすでに式を反転させています。ソースからは、YACCにピンを刺すようなsubstituteの動作がわかりませんでした...。

もちろん、私はまだここで推測しているのですが、はい、私は、あなたが substitute(3 -> y) から見て の観点からは、代入関数 という式は は常に y <- 3 例えば、この関数はあなたが 3 -> y . do_substitute は、Rで使用される99%のC関数と同様に、唯一の処理である SEXP 引数を扱います。 EXPRSXP の場合 3 -> y (== y <- 3 ) と思っています。これは、私が上記でR環境と構文解析プロセスを区別したときに言及したことです。特にパーサーが動き出すきっかけとなるものはないと思います - むしろ すべて を入力するとパースされます。私は 少し YACC / Bison パーサーについてさらに読みました。 ジェネレータ 昨夜、私が理解したところでは (つまり、これに賭けないでください)、Bison はあなたが定義した文法 ( .y ファイルで)定義した文法を使用して を生成します。 を生成します。つまり、入力の実際のパージングを行うC関数です。つまり、R セッションで入力されたものはすべて、まずこの C のパーシング関数によって処理され、次に R Environment (この用語は非常に大雑把に使っています) で実行される適切なアクションが委譲されるのです。このフェーズでは lhs -> rhs は次のように翻訳されます。 rhs <- lhs , ** から ^ などなど...。例えば、これはある1つの の表から抜粋したものです。 :

/* 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式の実際の評価まで)では、パーサーはすでに魔法をかけており、そのため、関数定義を得ることができない、ということです。 -> または ** のように、バックティックで囲んでください。