はじめに
こんにちは、事業会社でデータサイエンティストをやっている人です。
業務でも自分の研究でも基本的にはRを利用しています。
(もちろんSQLも書きますが)
この記事を書きました。
RはPythonとは違うプログラミング言語で、異なる開発方針を持っているため、PythonのユーザーはRの文法に違和感を感じるのは当たり前です。
このような記事を通じて、RとPythonのコミュニティの相互理解を促進できれば幸いです。
Python使いがキモいと思うRの挙動はまだ数えきれないほどあるので、また別の機会で紹介したいと思います。
では本題に入りましょう。
御指摘
記事の中で、@koguusさんから以下のコメントをいただきました。
別のコードになりますが
> `%>%` <- magrittr::`%>%`
> pipe_before_return <- function(x){
x %>%
sin %>%
return
123456789
}
> pipe_before_return(12)
[1] 123456789
あれ?sin関数の出力が123456789になっている、、、?
@koguusさんが指摘していただいたように、%>%の後にreturnをつなげても、結局一般的に我々が期待するreturnの「値を返却して関数の実行を終了させる」役割を果たしておらず、最後に実行された123456789が返却されてしまいました。
これはどういうことなのでしょうか?
思考回路
まずは、そもそも関数外でreturnを呼び出したらどうなるかを試しました:
> return(12)
Error: no function to return from, jumping to top level
まあ、エラーは出ますね。
ではprefix形式で%>%の挙動を確認してみましょう:
> `%>%`(12, return)
[1] 12
> `%>%`(19, return)
[1] 19
あれ?そのまま前の値が出ましたね。
しかも関数を定義していないのにエラーが出なかったんですね。
Rのreturnがおかしいのか、それとも%>%がおかしいのか?
あ、そういえばここでreturnに何か入れてみたらどうなるでしょう?
> `%>%`(19, return(12))
[1] 19
お?
エラーがなかっただけでなく、12の値もなんか無視されていますね。
では今度は定義していない変数を入れましょう!
> hahaidontevenexist
Error: object 'hahaidontevenexist' not found
> `%>%`(19, return(hahaidontevenexist))
[1] 19
あら。
これはそもそも%>%がreturnを評価していない可能性がありますね。
やはりソースコードを確認しましょう!
原因
これだとわからないので、勇気を出してC言語の方に行きましょう。
あ!理由がわかりました!
「For compatibility, allow last expression to be return(). Substitute it with . to avoid an error.」
エラーを避けるために、裏でreturnを「.」に変換しました。だからreturnはそもそも実行されておらず、ただ単に最後に実行されただけで返却されたということです。
なるほど、前回の記事で紹介した恐ろしい関数:
strange_function <- function(x){
x %>%
sum %>%
return
}
を書き方を許容するため、裏で恐ろしいことをやっているんですね、、、
考察
個人的には、returnを裏で「.」に変えるやり方はあまり好きではありません。開発者がreturn関数が実行されたと思っているのに、裏では全く違うことが起きているというのは、場合によってはデバッグしにくいエラーにつながる可能性もあります。
これはまさに恐ろしい挙動です。
これに対して、base Rに実装された新しいパイプ「|>」が右側にreturn関数を入れることをそもそも禁止しています。
magrittrパッケージのパイプは他にも色々キモい挙動があります。
たとえばパイプの左側の変数を右側の関数の複数の引数に入れる機能なんですが:
> 2 %>%
rnorm(n = 5, mean = ., sd = .)
[1] 1.565627 2.426024 1.528327 3.819079 3.667789
この規模の関数だったら問題ないですが、引数が多い場合、2は一体どこまで使われているかがわかりにくくなるため、お勧めしません。
base Rのplaceholder「_」は右側の関数の中で一回だけ使える仕様になっています。複数回使いたいなら別の関数を作るべきだと個人的に思います。
結論
前回の記事でも述べたように、Rは後方互換性を重視する言語のため、過去の機能をあまり捨てない開発方針を取っています。
なので今後もmagrittrのパイプとbase Rのパイプが共存することになりますが、個人的にはmagrittrのパイプのキモい挙動からの教訓を学んだbase Rのパイプの利用を推奨します。