pipeline operatorが使われているコードを読み解いてミタァ!


はじめに

2019/6/13、Ruby 2.7にpipeline operatorが入りました。
discussionが盛り上がっててTLでも各Rubyist達の様々な意見が飛び交ってます。
そんな中、@hanachin_さんのツイートを見かけました。

Ruby 2.7から追加される機能が積極的に使われていてすごく勉強になりそうだったので、まとめてみました。
認識間違っている・不足している箇所もあるかもしれないのでその際は教えてください。
何分はじめてのQiita記事なのでお手柔らかにお願いします。

Rubyのバージョン

2.7.0-dev

※2019/6/4時点でまだ2.7は開発中なので、2.7で新しく追加される機能の挙動は今後この記事とは変わってくる可能性があります。

本編

このコードは何をしているの?

まず@hanachin_さんのコードと下記のコードは等価です。

puts JSON.parse(Net::HTTP.get(URI.parse("https://api.github.com/repos/ruby/ruby"))).fetch("stargazers_count")

URLにGETでリクエストしてJSONを取得、そこからstargazers_countを取得して出力する、という処理です。

このコードを読むために必要な知識

従来からある機能

  • Object#itself
  • Method#>>
  • Object#then

2.7の新機能

  • .:
  • pipeline operator
  • Numbered parameters

1行目

"https://api.github.com/repos/ruby/ruby".:itself

Object#itself

self、つまりレシーバ自身を返します。
ここでは"https://api.github.com/repos/ruby/ruby"です。

参考:Object#itself

.:fuga

methodメソッドのショートハンドです。
hogefugaというMethodオブジェクトが欲しい場合は

hoge.:fuga

と書けます。

参考:Ruby 2.7の新機能メソッド参照演算子

1行目と等価なコード

"https://api.github.com/repos/ruby/ruby".method(:itself)

2行目〜5行目

|>>> URI.:parse
|>>> Net::HTTP.:get
|>>> JSON.:parse
|> call

pipeline operator

|>のことです。
メソッドの後の()が省略できる以外は.でメソッド呼び出すのと同じです。

Method#>>

>>の場合は左辺のMethodオブジェクトの戻り値を引数として右辺を実行します。
<<の場合は右辺の戻り値を引数として左辺のMethodオブジェクトを実行します。
なので2行目〜5行目のコードは下記のようなイメージです。

|>>> URI.:parse      # URI.parse(1行目の戻り値)を実行
|>>> Net::HTTP.:get  # Net::HTTP.get(URI.parseの戻り値)を実行
|>>> JSON.:parse     # JSON.parse(Net::HTTP.getの戻り値)を実行

参考:Ruby 2.6 の変更点 - Method と Proc

5行目までと等価なコード

-> {
  JSON.parse(Net::HTTP.get(URI.parse("https://api.github.com/repos/ruby/ruby")))
}.call

6行目

|> fetch("stargazers_count")

Hash#fetchで5行目までで取得したJSONのstargazers_countプロパティを取得しています。

6行目までと等価なコード

JSON.parse(Net::HTTP.get(URI.parse("https://api.github.com/repos/ruby/ruby"))).fetch("stargazers_count")

7行目

|> then { puts @1 }

Object#then

selfをブロック引数に渡してブロックを評価し、ブロックの評価結果を返します。
ここでは6行目で取得したJSONのプロパティstargazers_countselfとなります。

参考:Object#then

Numbered parameters

ブロック引数の定義を省略して@1@2@3... に多重代入できます。
ここでは@1には6行目の戻り値が入ってきます。

参考:Ruby 2.7 の Numbered parameters を試す

最終的な等価なコード

puts JSON.parse(Net::HTTP.get(URI.parse("https://api.github.com/repos/ruby/ruby"))).fetch("stargazers_count")

Ruby 2.7での追加機能超便利!

Ruby 2.7での追加機能、使いこなせれば気持ちよくコードが書けそうですね!
勉強にもなったしめでたしめでたし!・・・ではなかった

演算子がNG

このコミットでpipeline operatorの後に+-等の演算子を使えなくなりました。
使えなくなった演算子一覧はここのコードで確認できます。(opにあたる演算子が使えなくなりました)

※なぜpipeline operationの後に演算子がきてはダメなのか、理由はわからなかったのでQuoraで質問してみてるので返答くれば追記します。

2019/6/15 追記

@yukihiro_matzから返答いただけました!
Ruby2.7のpipeline operatorで+や-等の演算子をはじいたのはなぜなんでしょうか?

読みやすくない、むしろ読みにくいというのが主な理由とのことでした。

その結果

こうなりました。


pipeline operatorは()でラップしている?

上記のコードでcall.で呼び出すと思い通りの挙動になりません。
関数合成した結果ではなくJSON.:parseをレシーバにしてcallを呼び出してしまうからです。
一方、pipeline operatorだと思い通りの挙動をするので、下記のようにレシーバを()で囲って評価されてるのかな?という気がしてます。(コード読んだわけではないので確証はないです)

("https://api.github.com/repos/ruby/ruby".:itself >> URI.:parse >> Net::HTTP.:get >> JSON.:parse).call

最後に

pipeline operatorは賛否両論ありますが、Rubyistが幸せになれる結果になるといいですね
また、@hanachin_さんのコードのおかげで非常に勉強になりました、@hanachin_さんありがとうございます