formatR

自動的な R フォーマット

Yihui Xie

2021-04-24

1. インストール

formatR は、 CRAN からインストールすることができます。 最新バージョンは、 yihui.r-universe.dev からテストすることができます。

install.packages("formatR", repos = "http://cran.rstudio.com")
# or development version
options(repos = c(yihui = "https://yihui.r-universe.dev", CRAN = "https://cloud.r-project.org"))
install.packages("formatR")

または、github のことがわかるのであれば、 Github repository を checkout して、 ソースからインストールしてください。 このページは、常に開発バージョンです。

library(formatR)
sessionInfo()
## R version 3.6.3 (2020-02-29)
## Platform: x86_64-apple-darwin16.7.0 (64-bit)
## Running under: macOS Sierra 10.12.6
## 
## Matrix products: default
## BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
## LAPACK: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib
## 
## locale:
## [1] ja_JP.UTF-8/ja_JP.UTF-8/ja_JP.UTF-8/C/ja_JP.UTF-8/ja_JP.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods  
## [7] base     
## 
## other attached packages:
## [1] formatR_1.9
## 
## loaded via a namespace (and not attached):
##  [1] digest_0.6.27     R6_2.5.0          jsonlite_1.7.1   
##  [4] magrittr_2.0.1    evaluate_0.14     rlang_0.4.10     
##  [7] stringi_1.5.3     jquerylib_0.1.3   bslib_0.2.4      
## [10] rmarkdown_2.7     tools_3.6.3       stringr_1.4.0    
## [13] xfun_0.22         yaml_2.2.1        compiler_3.6.3   
## [16] htmltools_0.5.1.1 knitr_1.31        sass_0.3.1

2. R コードの再フォーマット

formatR パッケージは、Rのコードを再フォーマットして読みやすくするために設計されました。 主な働きは、関数 tidy_source() です。 機能は以下の通りです。

Below is an example of what tidy_source() can do. The source code is:

## comments are retained;
# a comment block will be reflowed if it contains long comments;
#' roxygen comments will not be wrapped in any case
1+1

if(TRUE){
x=1  # inline comments
}else{
x=2;print('Oh no... ask the right bracket to go away!')}
1*3 # one space before this comment will become two!
2+2+2    # only 'single quotes' are allowed in comments

lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100)))  ### a linear model
1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1  # comment after a long line
## here is a long long long long long long long long long long long long long comment that may be wrapped

上記のコードをクリップボードにコピーして、tidy_source(width.cutoff = 50) と入力すると、以下のようになります。

## comments are retained; a comment block will be
## reflowed if it contains long comments;
#' roxygen comments will not be wrapped in any case
1 + 1

if (TRUE) {
    x = 1  # inline comments
} else {
    x = 2
    print("Oh no... ask the right bracket to go away!")
}
1 * 3  # one space before this comment will become two!
2 + 2 + 2  # only 'single quotes' are allowed in comments

lm(y ~ x1 + x2, data = data.frame(y = rnorm(100), x1 = rnorm(100), 
    x2 = rnorm(100)))  ### a linear model
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 
    1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1  # comment after a long line
## here is a long long long long long long long long
## long long long long long comment that may be
## wrapped

tidy_source() の2つのアプリケーション:

3. グラフィカル・ユーザー・インターフェース

もしshinyパッケージがインストールされていれば、関数 tidy_app() で、 次のようにRコードを再フォーマットする Shiny アプリを起動することができま。 (live demo):

formatR::tidy_app()

フォーマット前の R ソースコード

Format ボタンを押したあと:

フォーマット後の R ソースコード

訳注: 上の二つの絵は、もともとウェブ上にありましたが、エラーが出たためにローカル(同一サイト内)へのリンクに変更しました。

4. コードを評価し、出力をコメントで隠す

Rで実行された他の人のコードからRのコードをコピーしようとすると、面倒なことがあります。 コードの先頭にプロンプト文字(通常は>)が付けられている場合、実行する前にすべてのプロンプト >+を手動で削除しなければなりません。 しかし、コードの出力を添付することができれば、読者がコードを理解するのに便利です. これが、関数 tidy_eval() の動機となっています。 この関数は、tidy_source() を使ってソースコードを再フォーマットし、コードを chunk で評価し、各 chunk の出力をコメントとして添付します。 元のソースコードを壊すことはありません。 以下にその例を示します。

set.seed(123)
tidy_eval(text = c("a<-1+1;a  # print the value", "matrix(rnorm(10),5)"))
a <- 1 + 1
a  # print the value
## [1] 2

matrix(rnorm(10), 5)
##             [,1]       [,2]
## [1,] -0.56047565  1.7150650
## [2,] -0.23017749  0.4609162
## [3,]  1.55870831 -1.2650612
## [4,]  0.07050839 -0.6868529
## [5,]  0.12928774 -0.4456620

デフォルトのソースは、tidy_source() のようにクリップボードからなので、コードをクリップボードにコピーして、これをRで実行するだけです。:

library(formatR)
tidy_eval()
# without specifying any arguments, it reads code from clipboard

5. ショーケース

セクション2のサンプルコードの続きで、tidy_source() で異なる引数を使用します。 arrow, blank, indent, brace.newline, comment などなど。

=<- に置換

if (TRUE) {
    x <- 1  # inline comments
} else {
    x <- 2
    print("Oh no... ask the right bracket to go away!")
}

空白行を捨てる

なお、5行目(空の行)は捨てられています。:

## comments are retained; a comment block will be reflowed if it
## contains long comments;
#' roxygen comments will not be wrapped in any case
1 + 1
if (TRUE) {
    x = 1  # inline comments
} else {
    x = 2
    print("Oh no... ask the right bracket to go away!")
}
1 * 3  # one space before this comment will become two!

再インデント (4つのスペースではなく2つ)

if (TRUE) {
  x = 1  # inline comments
} else {
  x = 2
  print("Oh no... ask the right bracket to go away!")
}

パイプ演算子 %>%

formatR 1.9以降、演算子 %>%, %T%, %$%, %<>% を含むコード行は、これらの演算子の後に自動的にラップされます。 例えば、

mtcars %>% subset(am == 0) %>% lm(mpg~hp, data=.)

これは、以下のようになります。:

mtcars %>%
    subset(am == 0) %>%
    lm(mpg ~ hp, data = .)

左の中括弧 { を新しい行に移動させる

if (TRUE)
{
    x = 1  # inline comments
} else
{
    x = 2
    print("Oh no... ask the right bracket to go away!")
}

コメントをラップしない

1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 
    1 + 1 + 1 + 1 + 1  # comment after a long line
## here is a long long long long long long long long long long long long long comment that may be wrapped

コメントを削除する

1 + 1
if (TRUE) {
    x = 1
} else {
    x = 2
    print("Oh no... ask the right bracket to go away!")
}
1 * 3
2 + 2 + 2
lm(y ~ x1 + x2, data = data.frame(y = rnorm(100), x1 = rnorm(100), 
    x2 = rnorm(100)))
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 
    1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1

6. 将来のための注記

このパッケージに使われているトリックはとても汚いものです。 formatR の中の関数を使うと危険なことがあるかもしれません。 次のセクションをよく読んで、コメントがどのように保存されるかを正確に知ってください。 失敗を避けるための最良の方法は、コメントを完全な行の中に入れるか、または完全なR式の後に入れることです。 以下は、tidy_source() が失敗するいくつかの既知のケースです。

不完全な式の後の行内コメント、あるいは ;

1 + 2 + ## comments after an incomplete line
    3 + 4
x <- ## this is not a complete expression
     5
x <- 1; # you should not use ; here!

不完全なR式の後にコメントがあるコードは、formatR で再フォーマットできません。 ちなみに、tidy_source() は、{ 以降のコメントを次の行に移動します。 例えば、

if (TRUE) {## comments
}

は、

if (TRUE) {
    ## comments
}

となります。

不適切な空行

Rコードの完全なチャンクを分離するために、しばしば空行が使用されます。 任意の空行は、引数 blank = TRUE の場合、tidy_source() でも失敗する可能性があります。 例えば、

if (TRUE)

{'this is a BAD style of R programming!'} else 'failure!'

if 文の後に空行があってはいけません。 もちろん、blank = FALSE でもこの場合は失敗しません。

? とコメント

クエスチョンマーク(?) を使ってヘルプページを表示することができますが、 formatR パッケージは ? を使ってコメントを含むコードを正しくフォーマットすることができません。 例えば、

?sd  # help on sd()

In this case, it is recommended to use the function help() instead of the short-hand version ?.

-> with comments

We can also use the right arrow -> for assignment, e.g. 1:10 -> x. I believe this flexibility is worthless, and it is amazing that a language has three assignment operators: <-, = and -> (whereas almost all other languages uses = for assignment). Bad news for formatR is that it is unable to format code using both -> and comments in a line, e.g.

この場合、短縮版の?ではなく、関数 help() を使うことをお勧めします。

-> とコメント

1:10 -> xのように、右矢印の->を代入に使うこともできます。 私はこの柔軟性には価値がないと思っていて、1つの言語に3つの代入演算子があるというのは驚きです。<-,=,->(他のほとんどの言語では代入に=を使用しています)。 **formatR** の悪い点は、一行の中に->` とコメントの両方を使ったコードをフォーマットできないことです。 例えば、

1:10 -> x  # assignment with right arrow

<-= を一貫して使用することをお勧めします。 もっと重要なことは 一貫性です。私はいつも = を使いますが、それは混乱を招かないからです。 人が fun(a = 1) を、変数 a1 を代入すると解釈することはありえないと思います。 <-はどこでも使えるのでより危険です。は、どこでも動作するので、より危険です (無意識のうちに、fun fun(a <- 1)で無意識に新しい変数aを作ってしまったかもしれません。 こちら を参照してください。) 唯一の欠点は 唯一の欠点は、ほとんどのRの人が<-`を使うので、他の人との共同作業が難しいかもしれません。

7. tidy_source()`は実際にどのように動作するのでしょうか?

一言で言えば、tidy_source(text = code) は基本的には deparse(parse(text = code)) です。 実際にはもっと複雑で、ただ一つの理由があります。

deparse(parse(text = "1+2-3*4/5 # a comment"))
## [1] "expression(1 + 2 - 3 * 4/5)"

コメントを保存する方法は、Rの式の中の文字列として保護することです。 例えば、ソースコードの中に1行のコメントがあるとします。:

  # asdf

以下のように最初にマスクされます。

invisible(".IDENTIFIER1  # asdf.IDENTIFIER2")

これは合法的なRの表現なので、base::parse()はこれを扱うことができ、偽装されたコメントを削除することはなくなります。 最終的には、元のコメントを復元するために識別子が削除されます。 つまり、文字列 invisible(".IDENTIFIER1.IDENTIFIER2") は空の文字列に置き換えられます。

インラインコメントは別の方法で処理されます: ハッシュ記号 # の前に2つのスペースが追加されます。

1+1#  comments

これは

1+1  #  comments

行内コメントはまず、コメント前のRコードと一緒になって、奇妙な操作に偽装されます。 これは本質的には無意味ですが、構文的には正しいのです。 例えば

1+1 %\b% "#  comments"

とすると、base::parse() がこの式を処理しますが、この場合も偽装されたコメントは削除されません。 最終的には、行内コメントも同様に解放されます。 (演算子 %\b% とそれを囲む二重引用符を削除します)。

これらのコメントに対する特別な扱いは、base::parse()base::depase() が、 すべてのコメントを削除する代償としてRコードを整頓することができるという事実に起因しています。

8. グローバル・オプション

tidy_source() のいくつかの引数をオーバーライドすることができるグローバル・オプションがあります。:

argument global option default
comment options('formatR.comment') TRUE
blank options('formatR.blank') TRUE
arrow options('formatR.arrow') FALSE
indent options('formatR.indent') 4
wrap options('formatR.wrap') TRUE
width.cutoff options('formatR.width') options('width')
brace.newline options('formatR.brace.newline') FALSE

また、wrap = TRUE の場合、1行の長いコメントは自動的に短いコメントに折り返されますが、roxygen のコメントは折り返されませんのでご注意ください (#' で始まるコメントはラップされません)。