magrittr 2.0のブログ記事で仄めかされていたように、base Rに(というかRの文法レベルで)ネイティブのパイプ演算子|>
が入るようです。
それがとうとう開発版のRに実装された、というツイートでTLがざわついていたので触ってみました。
#RStats community, really? It's been like 5 hours now and no one noticed the big news? 😛https://t.co/c4OXTO0WCw
— Henrik Bengtsson (@henrikbengtsson) 2020年12月4日
Thank you @LukeTierney4 @_lionelhenry @jimhester_ (who else?)#pipypipy pic.twitter.com/5P9QPe1H8a
Dockerでさくっとrocker/tidyverse:devel
を試すことにします。
なお、当然ですが開発版なので今後大きく変更されることもあります。
あくまで参考までに。
docker run --rm -it rocker/tidyverse:devel R
基本的な使い方
基本的な使い方は%>%
と同じです。
|>
の左にある表現を右の関数呼び出しの第一引数に放り込んでくれます。
以下の例でいうと、mtcars
がhead(2)
の2
を押しのけて第一引数に割り込み、head(mtcars, 2)
として評価される、という寸法です。
mtcars |> head(2) #> mpg cyl disp hp drat wt qsec vs am gear carb #> Mazda RX4 21 6 160 110 3.9 2.620 16.46 0 1 4 4 #> Mazda RX4 Wag 21 6 160 110 3.9 2.875 17.02 0 1 4 4
|>
と%>%
の違い: 右辺は関数呼び出ししか受け付けない
さて、基本は同じだと確認したところで、まずはニッチな違いから見ていきましょう。
%>%
にはいろいろ察していい感じに処理してくれる、みたいな機能がありました。
たとえば、%>%
には右辺が関数呼び出し(()
あり)ではなく関数オブジェクト(()
なし)でも動きました。
mtcars %>% head #> mpg cyl disp hp drat wt qsec vs am gear carb #> Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 #> Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4 #> Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1 #> Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 #> Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2 #> Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
ところが、|>
でこれは動きません。
動かない、というのも、実行されてエラーになるのではなく、文法レベルでエラーになるので reprex::reprex()
すら実行できません。
> reprex::reprex({ mtcars |> head }) Error: The pipe operator requires a function call or an anonymous function expression as RHS
不便かもしれませんが、個人的にはこれは好ましい挙動だと考えています。
%>%
の挙動は便利ですが、たとえば::
でパッケージ名を指定するとエラーになる、という困った問題があります。
mtcars %>% utils::head #> Error in .::utils: unused argument (head)
これは ::
も関数で utils::head
は関数呼び出しだ、ということに由来するのですが、ふつうのRユーザーにとっては謎のエラーすぎます。
なのでそもそもmagrittrは()
なしを許すべきではなかった、とmagrittr作者の人もたしかツイートしてたんですが(うろ覚え)、
あまりにも一般的になりすぎたので今さらbreaking changeを入れられない、ということで今に至っています。
|>
と%>%
の違い: |>
は文法要素
上で「文法レベルでエラーになる」と書きましたが、実は、|>
は関数ではありません。
私はこのツイートで知っただけなんですが、なるほどなーと思ったので紹介します。
実際、以下のようにコンソールに打ってみるとわかりますが|>
という関数は存在しません。
`|>`
#> Error: object '|>' not found
これは、->
と同じです。
->
は関数としては存在しません。
`->`
#> Error: object '->' not found
ではどうやって動いているのかというと、パーサによって、左辺と右辺を逆にして通常の<-
を使った式に変形する、ということが起こっています。
quote(1 -> a) #> a <- 1
|>
も同じで、パーサのレベルで式が変形されています。
quote(mtcars |> head()) #> head(mtcars)
つまり、magrittr 2.0でも%>%
はかなり高速になりましたが、|>
は関数呼び出しのオーバーヘッドすらない、ということです。
|>
と%>%
の違い: 第一引数以外に渡したいときは関数でラップする
|>
は、左辺を第一引数以外に渡したいときは少し面倒です。
例えば、rep()
に繰り返し回数を左辺から渡したい場合を考えてみましょう。rep()
は第一引数に繰り返される要素を、第二引数に繰り返す回数を取ります。
こんなとき、%>%
では.
を使うことで渡したい引数の場所を指定することができました。
2 %>% rep(1, .)
しかし、|>
は第一引数に渡すことしかできません。
なので、第二引数以降に渡すためには、次のように関数でラップして、そのラップした関数に渡してやる必要があります。
2 |> (function(n) rep(1, n))()
さすがにこれは冗長なので、その場で関数を定義する場合は関数呼び出し()
の省略を許すことにしているようです。
2 |> function(n) rep(1, n)
これでもちょっとfunction()
をタイプするのはめんどくさいですね。
そこで(このためだけなのかは謎ですが)、関数の定義に\()
という短縮表現が使えるようになりました。
\(引数) 内容
のように指定します。これを使うと、かなり短く書くことができます。
2 |> \(n) rep(1, n)
ちなみに\()
は|>
の外でも普通に使えます。これもパーサのレベルで処理されるもので、完全にfunction()
と同じです。
quote(\(x) x) #> function(x) x quote(\(x, y, ...) c(x, y, ...)) #> function(x, y, ...) c(x, y, ...)
なので、lapply()
の中で無名関数を作るのに使ったり、これでつくった関数を変数に代入したりできます。
(R-develメーリングリストでは、「これでもまだ長くね?」という話が出ているので、今後さらに変わるかもしれません)
追記(2020/12/5)
いろいろ触っていて気づいたんですが、[
とか中置演算子とか文法要素っぽい関数は受け付けないっぽいですね。
なぜこういうデザインなんだろう...
Curious... #rstats pic.twitter.com/UaXRTImFXV
— Hiroaki Yutani (@yutannihilat_en) 2020年12月4日
感想
まさかここで関数作成のショートカットまで出てくるとは思ってませんでしたが、%>%
と比べてだいぶシンプルに実装されてるなという印象です。
最終的にR 4.1のときにどうなっているかわかりませんが、待ち遠しいですね。