Note

3年後の自分のために書いています

RubyKaigi 2019 レポート & 所感

はじめに

RubyKaigi 2019 に参加してきました。自分の聞いた発表のレポートや所感を書いていきます。(現時点で Twitter でスライドが公開されているものは載せておきます)

自分の理解・関心の度合いによって、文量に差が出ていますがご容赦ください。

間違っているところなどあればご指摘いただけると幸いです。

各発表のスライドはドリコムさんの以下の記事がよくまとまっていると思うので貼っておきます(全て揃っているわけではありませんが)

先にまとめ

  • Ruby 自体に関する発表は型の話とパフォーマンス改善(並行・並列処理、 JIT コンパイラなど)の話が主だった
  • Ruby を用いた実践的な設計・実装やライブラリにまつわる発表(DSLの設計、API クライアント開発、RSpec など)などもあり、とても勉強になった
  • mruby 楽しい
  • (主に英語力と技術的な基礎知識のなさが原因で)理解できる発表の方が少なかったことが悔しくて英語を学び始め、技術書を5冊ポチった
  • 発表に対する理解度は低かったけど、とても良い刺激を受けられたので、このタイミングで RubyKaigi に行けてよかった

Day 1

Keynote: The Year of Concurrency

  • Ruby 自体に関する発表は型の話とパフォーマンス改善(並行・並列処理、 JIT コンパイラなど)の話が主だった

先にまとめ で 👆 と書いた通り、 Ruby 3 で導入する(しない)「型」と「パフォーマンス改善策」についての話がメインだった。

あとは新機能としてブロック変数を明示的に書かずとも @1 とか(まだ決まっていない)で参照できる Numbered Block Parametercase ~ in によるパターンマッチ構文の話などをしていた。

Ruby 3 Progress Report

静的解析とか Bundler とかキーワード引数の仕様変更の話をしていた気がする。

Terminal Editors For Ruby Core Toolchain

Ruby 2.7 からは irb が便利になりますよって話。

Determining Ruby Process Counts: Theory and Practice

英語がほとんど聞き取れなかったので、スライドや Twitter でのハッシュタグツイートで必死に補完していた。

  • Request Queue を理解するのが大事
    • New Relic を使っていれば LoadBalancer や Nginx で使った時間が見られる
    • ここら辺 かな?
  • AverageResponseTime x 4 x RequestArrivalRate = Ruby Process Count にするとよい
  • Ruby は GVL があるので thread 数でなく process 数で考えるべき
  • JRuby は parallel に実行できるので process 数ではなく thread 数で考えるべき

Woker の設定とか全然わかってないな。勉強しよう。

A Type-level Ruby Interpreter for Testing and Understanding

Type Profiler の話。

Type Profiler を使えば Ruby コードに対して静的解析を行い、実行時エラーを事前に検出したり、型定義を吐き出すことができるようになる。 型定義の出力はあくまでドキュメントとしてのみで Ruby に型注釈は導入しないということを何度も念押ししていた。

Fibers Are the Right Solution

パフォーマンスの高いアプリケーションを書くために Fiber を使おうよという話。理解度は低い。 発表の最初に並行性・並列性についての説明をしてくれていたので、もし動画がアップされたら再度観たい。

GraphQL Migration: A Proper Use Case for Metaprogramming?

REST API などから GraphQL API へ移行する際に、メタプロを使って動的に GraphQL の Type 定義を生成するようにするとよいかもね、という話。

肝心のメタプロの実装部分が出てこなかったのと、実際やるとしてもテストがむちゃくちゃ激しくなりそうなので、アプリケーション内に組み込むイメージは出来なかった。(自分の理解が足りないだけかもしれない)

Day 2

All bugfixes are incompatibilities

Ruby の安定版のメンテナンスの話。(そもそも Ruby では安定版メンテナーと開発版メンテナーが分かれているというのを初めて知った)

最近は昔と比べて互換性を意識してやってますよ、という話だった気がする。

終盤で「ほとんど使われない部分のバグ修正に対応するよりも、より多くのユーザに対するメリットを優先する」的な話をしていて、これは SaaS というかアプリケーション全般にも言えることだよな、と思った。

How RSpec works

RSpec 自体の実装の話。(RSpecGoogle の中の人が作ってるらしい)

rspce-core, rspec-mocks, rspec-expectations のそれぞれの役割と内部実装に触れていてむちゃくちゃ面白かった。

個人的に今回の RubyKaigi の中で一番プレゼンが上手いと思ったので、動画がアップされたら何度も観直して参考にしたい。

Ovto: Frontend web framework for Rubyists

俺が作った最高の Ruby フロントエンドフレームワークを紹介するぜ。というお話。

Opal をベースに作っているらしい。コードは読みやすかった。

State of Sorbet: A Type Checker for Ruby

Stripe のチームによる静的型解析器 Sorbet の話。

現在はプライベートβ版だが、もうすぐ OSS 化されるとのこと。ここ から触れる。

むちゃくちゃ便利そうだけど、 Type Profiler の発表の時に言ってた「Ruby に型注釈は導入しない」の流れとは反している気がする。

A light weight JIT compiler project for CRuby

既存の MJIT のパフォーマンス改善を主目的とした軽量 JIT 基盤開発プロジェクト(MIR プロジェクト)の話。 今回の RubyKaigi の中で一番理解度が低い発表。聞いてる時はほとんど何も分からなかった。

GCCLLVM の話が最初にでてきて、「あれ GCC って Ruby と関係あったっけ? LLVM ってなんだ?」ってなっていた。

何も分からなかったがゆえにこの発表が後述する技術書を買う一番のモチベーションになった。

What is Domain Specific Language?

RubyDSL を書くお話。

  • なぜ internal DSL は言語と見なされるのか?
    • 独自のセマンティクスを持っているから
  • なぜ DSL にすると読みやすいのか?
    • プログラマやそれ以外の人でもそのドメインの知識があればコードの意味を理解できるように設計されているから
  • どうすればよい DSL を設計できるか?
    • Ruby の黒魔術を適度に利用する :imp:

そういえばメタプロ Ruby にも「Ruby はその柔軟性ゆえに DSL を書くのに適している」というような記述があった気がする。

Lightning Talks

個人的に良いと思った2つの発表スライドを載せておきます。

Day 3

Ruby Committers vs the World

以下のような議題で公開開発会議が行われていた。

  • Range の () を省略したときでも良い感じに書くための演算子を導入したい(e.g. a..b |> each
  • 右代入を導入したい
  • Numbered Block Parameter をどの記号にするか?

個人的には Numbered Block Parameter も含めて 👆 全部不要では?と思っている勢です。

このセッションで「Ruby を作る人達は別のパラダイムの言語を知った上でそこからインスピレーションを受けて Ruby を開発しているんだ」ということを知り、後述する Haskell 本を買うモチベーションになった。

(partially) Non-volatile mruby

mruby のメモリ管理の話。

実はこの前夜 mruby のワークショップに行っていて、 mruby にだいぶハマっていたため、とても面白かった。

mruby で遊ぶ用のマイコンが欲しい。

Best practices in web API client development

Ruby で書く Web API クライアントのお話。 API クライアント開発におけるアンチパターンとベストプラクティスがだいぶ実践的で、とても勉強になった。

発表内で紹介されていた リファクタリング Ruby 読みたいけどめっちゃ高いので買うか悩む。

The future of the Bundled Bundler with RubyGems

Bundler の話。

Bundler を RubyGems にマージしたいと言っていたが、どういうことかあんまりわかっていない。 library rubygems のコードに組み込むということかな?

Reducing ActiveRecord memory consumption using Apache Arrow

Apache Arrow を使って ActiveRecord #pluck よりパフォーマンスのよいクエリ実行に成功したよ、という話。

マルチスレッド(10スレッド)の場合で、#pluck と比べてメモリ消費量は2倍になったが、実行時間は 1/5 になったとのこと。

The send-pop optimisation

send-pop を最適化して Ruby のパフォーマンスを改善したよという話。

send-pop とは、 Ruby のメソッドで必ず返される(それが使われるかどうかは別として)何らかの返り値について、それが使われなかった時に捨てる、という一連の操作のこと(メッセージを send したあとに使わなければ pop する)

結論から言うと、メソッドの返り値を使うかどうかのフラグをもたせて、そのフラグを見て後処理を最適化することで処理速度をアップさせたらしい。(やりたいことは分かったが、実装部分の説明では理解が追いつかなかった)

Optimization Techniques Used by the Benchmark Winners

スピード狂によるスピード狂のための Ruby で究極の速さを追い求める話。

「それ、C言語でいいのでは?」 とか絶対に言えない雰囲気で、可読性完全無視で Ruby で最高のパフォーマンスを発揮する手法について語られていた。

発表されていたご本人が一番楽しそうでなによりでした ^^

所感とか

  • 刺激を受けて技術書を 5 冊買いました。
  • 半分ぐらい英語の発表でしたが全然聞き取れず、自身の英語力の無さを痛感したので、英語を学び始めました。(まずはリスニングからかなと思っています)
    • Web 上のドキュメントを読む分には Google 翻訳さえあればぶっちゃけそんなに困らないが、こういうリアルなカンファレンスや動画コンテンツや活字での情報収集が英語でできるのとできないのとでは、全然情報量が違ってくると思った。 あと、カンファレンスに登壇まではできずとも英語で鋭い質問ができるとかっこいいなという憧れもある。
  • mruby で遊ぶ用のマイコンが欲しい。
  • 今見返すとレポートというより感想文ですね、おそらく理解度が低いためです。もっと勉強します。

ActiveRecord の `#all`, `#find_each`, `#find_in_batches`, `#in_batches` の違いについて

表題の件、ずっと曖昧に理解していた。

1.2 複数のオブジェクトをバッチで取り出す - Active Record クエリインターフェイス - Rails ガイド

Rails のコードも読みながら ↑ の内容にプラスα してまとめる。

結論

処理するレコードが 1000 件以下なら何使っても一緒。

1000 件以上になってくると、 #all ではなく、 #find_each, #find_in_batches, #in_batches など ActiveRecord::Batches のメソッドを用途に合わせて使い分けていくのが良い。

#all

Returns an ActiveRecord::Relation scope object.

該当コードは ここActiveRecord::Relation オブジェクトを返す。

users = User.all
users.size # Fires "select count(*) from  users" and returns the count
users.each {|user| puts user.name } # Fires "select * from users" and loads user objects

テーブル全体を一度に取り出し、レコード 1 行ごとにオブジェクトを生成し、そのオブジェクトの配列をメモリに載せるので、メモリを圧迫する可能性がある。

#find_each, #find_in_batches, #in_batches

大事なのはここから。

#find_each, #find_in_batches, #in_batchesActiveRecord::Batches として定義されており、バッチサイズで指定したレコード数ごとに処理できるため、大量のレコードを扱う際にメモリ消費量を削減できる。デフォルトのバッチサイズが 1000 件なので、それよりレコード数が少ない場合は #all を使っても変わらない。

実は上記メソッドの関係は #find_each の中で #find_in_batches を呼び出し、 #find_in_batches の中で #in_batches を呼び出す、という関係になっている。

柔軟性でいうと #in_batches > #find_in_batches > #find_each というわけだ。

#in_batches から見ていく。

#in_batches

Yields ActiveRecord::Relation objects to work with a batch of records.

該当コードは ここ。長いので読めてない。 ActiveRecord::Relation オブジェクトをブロックに渡すので、一番柔軟な使い方ができる。

User.where("age > 20").in_batches do |relation|
  # Relation オブジェクトに対する処理が書ける
  relation.destroy_all
end

relation.where でつなげて絞り込みしたりとか relation.pluck 的な形でも使える。

#find_in_batches

Yields each batch of records that was found by the find options as an array.

該当コードは ここ#in_batches で取り出したバッチを #to_a してブロックに渡す。

User.where("age > 20").find_in_batches do |users|
  # バッチ前処理が書ける
  # 配列に対する処理が書ける(Relation オブジェクトに対する処理は書けない)
  users.each { |user| user.drink_alcohol! }
  # バッチ後処理も書ける
end

#find_each

The find_each method uses find_in_batches with a batch size of 1000 (or as specified by the :batch_size option).

該当コードは ここ#find_in_batches で取得したモデルの配列を #each してブロックに渡す。

User.where("age > 20").find_each do |user|
  # バッチ処理の前後に処理がないなら一番スッキリ書ける
  user.drink_alcohol!
end

実装から学ぶ HTTP 通信 〜 Ruby 標準ライブラリ編〜

社内勉強会のコピペ。


HTTP とは

HTML など をやり取りする時の決まりごと

Web は URL で指定した HTML など を HTTP という決まりごとに沿ってやり取りしている

詳しくは Webを支える技術 という本に書いてあります

今日話すこと

  • Ruby の標準ライブラリで HTTP 通信がどう実装されているかをコードリーディングしながら見ていきます(Ruby の話が若干多めかも?)

今日話さないこと

登場する標準ライブラリ

先に結論

HTTP 通信は実装的にはネットワーク越しに(ソケットを通して?)ファイルの読み書きをしているだけ!

たとえばこんなコード

require 'webrick'

srv = WEBrick::HTTPServer.new({ :DocumentRoot => './',
                                :BindAddress => '127.0.0.1',
                                :Port => 20080})
srv.mount('/', WEBrick::HTTPServlet::FileHandler, 'index.html')
trap("INT"){ srv.shutdown }
srv.start
<div>
  Hello!
</div>
require 'net/http'
require 'uri'

uri = URI.parse('http://localhost:20080/')
puts Net::HTTP.get(uri)

実行するとこうなります

$ ruby http_server.rb
[2019-03-22 07:40:24] INFO  WEBrick 1.4.2
[2019-03-22 07:40:24] INFO  ruby 2.5.3 (2018-10-18) [x86_64-darwin18]
[2019-03-22 07:40:24] INFO  WEBrick::HTTPServer#start: pid=1786 port=20080

$ ruby http_client.rb
<div>
  Hello!
</div>

この時何が起きているか、実装を覗いてみる。

Webrick サーバ起動まで@webrick

URI のパース@uri

HTTP クライアントが TCP コネクションを開くとこまで@nethttp

HTTP (GET) リクエスト送信まで@nethttp

HTTP リクエストのパースまで@webrick

HTTP レスポンスの作成まで@webrick

HTTP クライアントでレスポンスを表示するまで@nethttp

  • HTTP#transport_request に戻って HTTPResponse.read_new とかでレスポンスを読み込む
  • 読み込まれたレスポンスがいろいろ通って HTTP.get の中の #get_response の返り値としてもどってきて、その body を取り出している…!!!

結論

HTTP 通信は実装的にはネットワーク越しに(ソケットを通して?)ファイルの読み書きをしているだけ!

graphql-ruby の lib/graphql/schema.rb のコードに興奮した

GraphQL はほとんど関係なく Ruby の話です。

GraphQL Ruby を実行する時はだいたいこんな感じで使う。

# app/graphql/my_schema.rb
class MySchema < GraphQL::Schema
  # ...
end

# app/controllers/graphql_controller.rb
result = MySchema.execute(
  params[:query],
  variables: params[:variables],
  context: { current_user: current_user },
)
render json: result

だが、MySchema の親クラスである GraphQL::Schema (lib/graphql/schema.rb) のコード を読んでもインスタンスメソッドの GraphQL::Schema#execute しか定義されていない。

呼び出し側でどうして MySchema.execute(args) のように特異メソッド的に呼べるのだろうと不思議だった。

以下が答えでした。

require 'forwardable'

class MyClass
  alias :my_definition :itself
  
  def execute(arg)
    arg
  end
  
  class << self
    extend Forwardable

    def_delegator :my_definition, :execute

    def my_definition
      self.new
    end
  end
end

> MyClass.execute('hoge')
#=> "hoge"

興奮した。

参考

instance method Object#itself (Ruby 2.6.0)

恒等写像 - Wikipedia

クラス/メソッドの定義/alias (Ruby 2.6.0)

module Forwardable (Ruby 2.6.0)

追記

alias :my_definition :itself はこの例には余計だった。

lib/graphql/schema.rb で定義している場所と理由。

こっちはもう少し調べないとわからなさそうです。

ていうかこの例に関していうと委譲がポイントでしたね。

require 'forwardable'

class MyClass
  def execute(arg)
    arg
  end
  
  class << self
    extend Forwardable

    def_delegator :my_definition, :execute, :my_execute

    def my_definition
      self.new
    end
  end
end

> MyClass.my_execute('hoge')
#=> "hoge"
> MyClass.instance_methods(false)
#=> [:execute]
> MyClass.singleton_methods(false)
#=> [:my_execute, :my_definition]

ちょっとスッキリしましたが、委譲難しい。

追記の参考

instance method Forwardable#def_delegator (Ruby 2.6.0)

instance method Module#instance_methods (Ruby 2.6.0)

instance method Object#singleton_methods (Ruby 2.6.0)

さらに追記

require 'forwardable'

class MyClass
  alias :my_definition :itself
  
  def execute(arg)
    arg
  end
  
  class << self
    extend Forwardable

    def_delegator :my_definition, :execute, :my_execute

    def my_definition
      self.new
    end
  end
end

> MyClass.my_execute('hoge')
#=> "hoge"
> MyClass.instance_methods(false)
#=> [:execute, :my_definition]
> MyClass.singleton_methods(false)
#=> [:my_execute, :my_definition]

alias :my_definition :itself を追加することで、 MyClass#my_definition が定義されるのか。

けっこうスッキリしたので、今日はここまでにしておきます。

Ruby の Array#each で空の配列をレシーバに取るとブロック内は実行されない

タイトルのまんまですが。

nil が回ったりするのかなと勝手に思ってました。

# レシーバが空の配列だとブロック内は実行されない
[].each { puts 'hoge'} #=> []
[].each { |i| puts i } #=> []
# Hash#each も同じく
{}.each { puts 'hoge'} #=> {}
{}.each { |k, v| puts k, v } #=> {}
# ブロック無しの場合に返す Enumerator オブジェクトももちろん空
e = [].each #=> #<Enumerator: ...>
e.each { |i| puts i} #=> []

参考

あんまり詳細載ってないけれど。

instance method Array#each (Ruby 2.6.0)

instance method Hash#each (Ruby 2.6.0)

Git で既存のリポジトリを複製して別名のリポジトリとして始める方法

最短かつスマートな方法を考えた結果こうなった。

## あらかじめ GitHub で hoge リポジトリを新規作成しておく
$ git clone {複製元リポジトリのURL}.git hoge
$ cd hoge
$ git remote set-url origin https://github.com/{user_name}/hoge.git
$ git push origin master

おまけ

最初は以下のようにやっていたが、職場の方に clone してくるときに root ディレクトリの名前を指定できるということを教えていただいた。

## あらかじめ GitHub で hoge リポジトリを新規作成しておく
$ mkdir hoge
$ cd hoge
$ git clone {複製元リポジトリのURL}.git .
$ git remote set-url origin https://github.com/{user_name}/hoge.git
$ git push origin master

Ruby 2.6 の変更点で個人的に気になったところをまとめておく

サンプルコードは参考リンクに豊富に載ってるので、ほぼ省略しています。

ローカル変数の shadowing 警告を削除

Effective Ruby の項目5で出てきたやつ、なくなったんですね。

to_h がブロックを受け取れるように

['Hoge', 'Fuga'].to_h { |x| [x.upcase, x.downcase] } みたいにブロック内で [key, value] となるような配列を返すと、それを要素としたハッシュが生成される。

Enumerable#chain, Enumerator#+

イテレータを連鎖させるやつ、便利そう。

Kernel#yield_self の別名に Kernel#then が導入された

そもそも Kernel#yield_self を知らなかった…レシーバをブロック引数として、ブロックの結果がそのまま返り値になるやつ。

Object#tap と似てるなーと思ったら、#tapself を返すんですね。

ref. instance method Object#tap (Ruby 2.6.0)

:exception オプションの導入

メソッドの実行失敗時に true なら例外を吐かせ、false なら nil を返す。

Array#union, Array#difference

これまで |- 演算子で実現していた配列の和集合と差集合を返すやつ、こちらは複数の引数を同時に取れる。

Hash#merge, Hash#merge! が複数の引数を受け取れるように

{k1: :v1}.merge({k2: :v2}).merge({k3: :v3}) と書かなくてよくなる。

String#split がブロックを受け取れるように

ブロック引数には分割された部分文字列が順に渡される。ブロックを使った場合、 #split メソッド自体の返り値はレシーバの文字列になるので注意が必要そう。

参考

プロと読み解く Ruby 2.6 NEWS ファイル - クックパッド開発者ブログ

サンプルコードでわかる!Ruby 2.6の主な新機能と変更点 - Qiita

ruby/NEWS at v2_6_0 · ruby/ruby · GitHub

Ruby 2.6.0 Released

おまけ

いろいろ調べてたら Ruby の Object クラスと Kernel モジュールの関係が曖昧になってきたので、ついでに整理。

以下の Kernel モジュールの要約がとてもよくまとまっている。

全てのクラスから参照できるメソッドを定義しているモジュール。 Object クラスはこのモジュールをインクルードしています。

Object クラスのメソッドは実際にはこのモジュールで定義されています。 これはトップレベルでのメソッドの再定義に対応するためです。

ref. module Kernel (Ruby 2.6.0)