BuriKaigi2021 で Ruby 2.0 〜 Ruby 3.0 までの話をしてきた
はい、ということで開催されてからだいぶ時間が経っちゃったんですが先月末に開催された BuriKaigi2021 で Ruby 2.0 〜 Ruby 3.0 の話をしてきました。
Ruby 2.0 から Ruby 3.0 を駆け足で振り返る
と、ここでならいつも雑に振り返りを行うのですが以下のブログに影響されてもう少し掘り下げてまとめてみようかと思います。
登壇するきっかけ
元々はわたしがよく参加している勉強会に BuriKaigi の関係者の方が参加していることが多くて『今年はコロナで大変だけど BuriKaigi をやりたいよねー』っていう話を前からしてはいたんですよね。
わたし自身は BuriKaigi 自体参加したことがなかったんですが『どうせなら登壇してみたいなー』みたいには前々からちょっと思っていました。
で、12月の頭ぐらいに『来年 BuriKaigi やりますー』って話を聞いて『○○○みたいな話をしてみたいんですが登壇者の募集とかってどうなっています?』みたいな感じで前から温めていたネタで打診してみました。
その時はまだ確定ではなかったんですが 12月下旬ぐらいに GO サインがでて登壇することが確定しました。
まあ完全に勢いとコネですね。人生にだいじだいじ。
テーマ選定
わたしは主に LT の場で話すことが多いのですが、その時のテーマ選定はだいたい
- わたしが話したい内容か
- 参加者に刺さる内容か
- 時間内で話せそうか
あたりを基準として決めることが多いです。
具体的に『何を話すのか』は直前まで悩んでいることが多くて LT を申し込む時点ではキマってないことの方がおおいです。
本当の意味で『その時に話したいこと』を話しています。そっちのほうがモチベーションが上がりますしねー。
ただし、今回の BuriKaigi2021 の場合は『最初にこういう内容で登壇したい』という形で打診をしたので『最初に話す内容を決めた』っていう点でいつもとはちょっと違っていました。珍しい。
具体的なテーマ選定
今回の BuriKaigi2021 では最初から以下のような基準で話したいなーと思っていました。
- キャッチーな話をしたい
- あんまり Ruby のコアな話はしない
- BuriKaigi の参加者層的な意味で
- けど Ruby をもっと知ってもらいたい!
- せっかくなので Ruby 3.0 の話をしたいなあ…
特に『せっかくRuby 3.0 がリリースされる(された)のでその話もしつつ普段は Ruby を知らない人にも興味がある内容を話したいなー』という点を重点的に考えていましたね。
そんなことを考えている時に『そういやわたしって Ruby 2.5 から本格的に Ruby をはじめたけどそれ以前のバージョンはよくしらんなーなんか Ruby の軌跡みたいなの話せると面白いのかなー』と思って『あーー『Ruby 2.0 〜 Ruby 3.0 で Ruby がどう変わってきたのか』みたいなのを話すと面白そうかなーー』っていう感じでテーマが漠然と決まった感じですね。
とりあえず『Ruby 2.0 〜 Ruby 3.0 のリリースノートをまとめて話そ〜〜』みたいに雑に決めました。
テーマの深堀り
ざっくりと話すことを決めたのでここでは具体的にどんな内容を話すのか考えていきます。
わたしの場合は実際にスライドを書くまでにある程度話したい内容を箇条書きにして書き溜めて行くことが多いです。
なので今回も決定してから登壇の 1週間前まで手元で話したいことを以下のような感じで書き溜めておきました。
- 自己紹介
- Ruby 2.0 ~ 3.0 までを振り返る
- まとめ
こんな感じですね。
登壇時間は 30分だったので各 Ruby のバージョンを 2分で話すと 2 x 9 で 18分になります。
残り 12分を穴埋めするために Ruby とは
と Ruby 2.0 以前の歴史
みたいな話も追加して話すことにしました。
更にどうせなら Ruby 3.0 のことも少し話したいなーと思い以下のように追加しました。
うーん、ちょっと 30分だと心もとなくなってきました。
で、ここからは裏話なんですが最初は登壇時間が 30分だったんですがわたしの後の枠が空いていたので +15分してもらって合計45分の時間に延長してもらいました。 いえーい。
なので、更に話すことを追加して最終的には以下のようなアウトラインが完成しました。
- 自己紹介
- Ruby とは
- Ruby 2.0 以前の歴史
- Ruby 2.0 ~ 3.0 までを振り返る
- 最近のモダンな機能紹介・デモ
- パターンマッチ
- 1行 in
- 右代入
- Ractor
- 型周りの話
- irb
- まとめ
ポイントとしては irb のデモを最後に入れたことですね。
時間が余れば irb のデモを長くすればいいですし、時間が足りなければそこを短くすることで時間を調整することができると思っていました。
まあ実際はガバガバだったんですけどね…。
これを元にしてスライドを書いていきます。
スライドを作る
上記のアウトラインを元にしてスライドを書いていきます。
実際にスライド作成に取り掛かったのは登壇の1週間ぐらい前になります。
スライド作成ツール
スライド作成はいつも reveal.js で作っています。
markdown でシュッとスライドを書くことができて、なおかつミニマムな構成で完結しているのが気に入っています。
レイアウトとかも css などをいじれば調整できますしね。
スライド作成と練習
今回のスライドはちょっと量が多かったのでまずはかける項目から書いていきました。
Ruby 2.0 ~ 3.0 までを振り返る
はひたすら当時のリリースノートを参考にして Ruby を知らない人でもわかるような機能をピックアップしてスライドに書いていきました。
逆にいえば Ruby を使っている人しかわからないような細かい変更はあんまり書かないように心がけていましたね。
だいたい各バージョンが 1〜2分ぐらいで収まるような量にしつつ、わかりやすいようになるべくサンプルコードも記述していきました。
書くのに一番苦労したのはやっぱり Ruby 2.0 あたりの話ですね。
最近の Ruby の動向はリアルタイムで追っているので経緯とか把握していることが多いのですが Ruby 2.0 の時代は全く知らないので調べるのに苦労しました。
あと単純に当時の資料とかを読んでると面白くてスライド書かないでずーっと資料を読み漁っていましたね…。
Ruby 2.0 以前の歴史
あたりの話も同様に昔話を聞いてるような感じで調べつつまとめていました。
最近のモダンな機能紹介・デモ
に関しては最近いろんなところで話をしているので既存のスライドから拝借して書いたのでそこまで時間はかかりませんでした。
そんな感じで内容はひたすら書いていく感じで構成とか内容自体はそんなに変更せずに書いていきました。
スライド自体が完成したのは確か前日の夜ぐらいでわたしにしては早めに完成していましたね。
練習に関しては『喋り慣れてるからまあそこまで練習しなくても大丈夫やろガハハ』って感じでそこまでやっていませんね。
それを元にして以下のように 残り時間
経過時間
を書き留めて置きます。
- 開始 (残り時間 45分) (経過時間 0分)
- 自己紹介
- Ruby とは
- Ruby 2.0 以前の歴史 (37分) (8分)
- Ruby 2.0 ~ 3.0 までを振り返る
- 最近のモダンな機能紹介・デモ (20分) (25分)
- パターンマッチ
- 1行 in (15分) (30分)
- 右代入
- Ractor
- 型周りの話
- irb (5分) (40分)
- まとめ
登壇中はこれを見ながら残り時間を確認していました。
発表前にやること
うちの環境は2画面なんですが登壇中に見る画面には、
- スライド
- タイマー
- (必要であれば)エディタ
みたいに最低限必要な情報のみ見えるようにしています。
で、もう一方の画面には Twitter や Zoom のチャットなど聞いてる人の声がわかるような形にしています。
あとは水を用意したりとかですかねえ。
あ、あと見づらい人用にスライドは発表前に公開するようにしています。
発表中にやること
スライドを見ながら話しつつタイマーで残り時間を確認しつつ余裕があれば Twitter で意見をみつつって感じで登壇しています。
流石に最近はスライドを見ながらタイマーを見るのは慣れてきましたねえ。
喋り始めは緊張するんですが喋ってる最中はもう殆ど緊張する事はなくなっています。
発表後にやること
ひたすら Twitter を見て気になる発言には個別に反応するようにしています。
なんかそっちのほうが盛り上がっている感があるのとやっぱり何かしらのフィードバックがもらえると嬉しいのでそこはちゃんとこっちもフィードバックするようにしています。
発表を終えてみて
機会があれば BuriKaigi で話してみたいなーと思っていたのでそれが達成できて個人的にはかなり満足度が高かったです。
特にこの手の大きなカンファレンスはオフラインだとなかなか参加しづらいのでオンラインで登壇できる環境っていうのは個人的にはめちゃくちゃありがたいんですよねー。
また機会があれば参加してみたいです。
運営の方々、参加者の方々、ありがとうございましたー!!!
2021/02/25 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[PR #4193] Add Hash#delete_at
Hash#delete_at
を追加する提案- bugs.ruby にまだチケットはなさそう
- 特定のキーの要素を取り除いて、取り除いた要素を返すメソッド
hash = { a: true, b: false, c: nil } # hash から直接要素を取り除きつつ、取り除いた値を返す a, c = hash.delete_at(:a, :c) # => [ true, nil ] hash # => { b: false } # Hash#except は取り除かれた結果を返す hash = { a: true, b: false, c: nil } hash = hash.except(:a, :c) p hash # {:b=>false}
- 取り除いた要素を参照したい場合は便利そう
[PR #207] Support pattern matching in CSV rows
CSV::Row
をパターンマッチに対応させる PR- CSV の行ごとにパターンマッチできる
- 以下のようにヘッダーカラムがキーで要素が値になる
header = [:id, :name, :age] case CSV::Row.new(header, [1, "homu", 14]) in name:, age: name # => "homu" age # => 14 end
- これは普通に便利そう
- こういうパターンマッチ対応って今後流行っていくんですかねー
[Bug #17590] M.prepend M
has hidden side effect
- 先週話していたバグ
- 以下のように
M.prepend M
を呼んだ場合に副作用あるバグが修正された
module M; end class C; end C.prepend M C.include M # これを読んだ時に失敗するが、副作用があった M.prepend M rescue nil module M2; end M2.prepend M C.include M2 p C.ancestors # 3.0 => [M, C, M2, M, M2, Object, Kernel, BasicObject] # 3.1 => [M, C, M2, Object, Kernel, BasicObject]
[Bug #17649] defined?
invokes method once for each syntactic element around it
defined?
の式で複数回メソッドが呼ばれることがあるというバグ報告- 以下の例だと
x
メソッドがdefined?
時に複数回呼ばれることがある
public def x $times_called += 1 end def times_called $times_called = 0 yield $times_called end # without `defined?` times_called { x } # => 1 times_called { -x } # => 1 times_called { --x } # => 1 times_called { ---x } # => 1 times_called { x+0+0 } # => 1 times_called { x.pred.pred } # => 1 times_called { x.x.x.x.x.x } # => 6 # with `defined?` times_called { defined? x } # => 0 times_called { defined? -x } # => 1 times_called { defined? --x } # => 2 times_called { defined? ---x } # => 3 times_called { defined? x+0+0 } # => 2 times_called { defined? x.pred.pred } # => 2 times_called { defined? x.x.x.x.x.x } # => 15
- これは
defined? a.b.c.d
を呼び出した時にa
a.b
a.b.c
a.b.c.d
が個別に呼び出されしまっているからのようです
puts RubyVM::InstructionSequence.new("defined? a.b.c.d").disasm __END__ output: == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,16)> (catch: TRUE) == catch table | catch type: rescue st: 0001 ed: 0039 sp: 0000 cont: 0041 | == disasm: #<ISeq:defined guard in <compiled>@<compiled>:0 (0,0)-(-1,-1)> (catch: FALSE) | local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) | [ 1] $!@0 | 0000 putnil | 0001 leave |------------------------------------------------------------------------ 0000 putnil ( 1)[Li] 0001 putself 0002 defined func, :a, false 0006 branchunless 41 0008 putself 0009 opt_send_without_block <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0011 defined method, :b, false 0015 branchunless 41 0017 putself 0018 opt_send_without_block <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0020 opt_send_without_block <calldata!mid:b, argc:0, ARGS_SIMPLE> 0022 defined method, :c, false 0026 branchunless 41 0028 putself 0029 opt_send_without_block <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0031 opt_send_without_block <calldata!mid:b, argc:0, ARGS_SIMPLE> 0033 opt_send_without_block <calldata!mid:c, argc:0, ARGS_SIMPLE> 0035 defined method, :d, true 0039 swap 0040 pop 0041 leave
- 修正 PR が投げられているので特に問題がなければ修正されそう
2021/02/18 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Misc #17591] Test frameworks and REPLs do not show deprecation warnings by default
- テストフレームワーク(RSpec や test-unite)や REPL(irb とか)で非推奨の警告をデフォルトで出すようにしよう、というチケット
- 既に
test-unit 3.4.0
ではデフォルトで有効になっており RSpec でも同様のチケットが立てられている- https://github.com/rspec/rspec-core/issues/2867
- issues のやり取りを見ていると RSpec 側ではそういう設定は行われなさそうな雰囲気
- また irb に関しては『初心者が使うと混乱する』という理由で同意はされていないようです
- 個人的には Ruby 自体が『デフォルトでは非推奨の警告を出さないように決めた』ので基本的には(少なくとも組み込みの範囲では)デフォルトでは出さないようにしてほしい気持ちが強い
- このケースは警告がでて、このケースは警告がでないみたいにすると一貫性もないし、ユーザは混乱するだけ
- Ruby 2.7.2 の世界では個々で必要に応じて警告を出すようにすべき
[Bug #17590] M.prepend M
has hidden side effect
M.prepend M
を呼び出すとエラーになるが副作用があるというバグ報告- 以下のように
M.prepend M
を呼んだ場合とそうでない場合で差異がある
module M; end class C; end C.prepend M C.include M module M2; end M2.prepend M C.include M2 # M.prepend M を呼んでない場合 p C.ancestors # => [M, C, M2, Object, Kernel, BasicObject] M.prepend M rescue nil module M3; end M3.prepend M C.include M3 # M.prepend M を呼んだ場合 # M が複数追加されている… p C.ancestors # => [M, C, M3, M, M3, M2, Object, Kernel, BasicObject]
- これには2つの問題があるようで、1つは以下のように
M.prepend M
するとM.ancestors # => [M, M]
となる問題
module M end # 継承リストは自身だけ pp M.ancestors # => [M] begin # 自身を prepend するとエラーになる # これは Ruby 2.7 でも 3.0 でも同じ M.prepend M rescue => e puts "error : #{e.message}" # => error : cyclic prepend detected end # M.prepend M はエラーになるが Ruby 3.0 では副作用がある # 3.1-dev では修正済み pp M.ancestors # 2.7 => [M] # 3.0 => [M, M] # 3.1 => [M]
- この問題自体は既に修正されている
- ただし、元々の例題の問題は残ったままになっている
- これは 37e6c83609ac9d4c30ca4660ee16701e53cf82a3 で最適化された影響らしい
Ruby で ["homu", "mami", "mado"].sum を呼ぶとエラーになる
今日の知見です。
Array#sum
メソッドは要素を #+
で結合してその結果を返します。
p [1, 3, 5, 7].sum # => 16
また Array#sum
に引数を渡すことで足し込む値の初期値を指定することもできます。
p [1, 3, 5, 7].sum(100) # => 116
["homu", "mami", "mado"].sum
を呼ぶとエラーになる
では要素が文字列の場合はどうでしょうか。
残念ながら要素が文字列の場合はエラーになってしまいます。
# error: `+': String can't be coerced into Integer (TypeError) pp ["homu", "mami", "mado"].sum
これは Array#sum
が内部で使用している初期値に問題があります。
Array#sum
は足し込むときの初期値として 0
を使用しています。
なので最初に足し込む際に 0 + "homu"
みたいな処理が実行されてしまいエラーになってしまいます。
これを回避する場合は Array#sum
に明示的に初期値 ""
を渡すことで回避することができます。
# OK pp ["homu", "mami", "mado"].sum("") # => "homumamimado" # 任意の要素を初期値にもできる pp ["homu", "mami", "mado"].sum("joined : ") # => "joined : homumamimado"
なぜエラーになるのかの原因がわかっているならいいんですがいきなり String can't be coerced into Integer (TypeError)
ってエラーになってもぎょっとしますよね。
ちなみに単に文字列を結合したい場合は Array#join
が利用できます。
pp ["homu", "mami", "mado"].join # => "homumamimado"
2021/02/11 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Bug #17424] Interactive Ruby で Object#method を再定義して任意の文字を入力するとエラーが発生する
- 表題の通りでなんですが
irb
上で#method
を再定義するとクラッシュするというバグ報告です - これは
irb
上で#method
を定義するとKernel#method
よりも優先順位が高いObject#method
が定義されてしまい、Reline でそのObject#method
を使用しているためです - これは仕様でチケットは閉じられています
- まあしょうがなさそう
[Feature #7394] Enumerable#find ifnone parameter could be non-callable
Enumerable#find
は要素が見つからなかった場合の処理をフォールバックすることができる
# 見つからなかった場合に第一引数の proc を呼び出す p [1, 3, 5].find(-> { "none" }, &:even?) # => "noge"
- 現状は
Proc
オブジェクトを渡すがこれをそれ以外のオブジェクトを渡せるようにする提案
# 第一引数の値がそのまま返ってくる p [1, 3, 5].find("none", &:even?) # => "noge"
- コメントでは以下のような既存の書き方と比較して何がよいのか指摘されている
p [1, 3, 5].find(&:even?) || "none"
[Feature #17608] Compact and sum in one step
- 次のように
Array#sum
を行う場合に#compact
を介して行うことがある
a = [1, nil, 2, 3] a.sum # !> TypeError a.compact.sum # => 6 a.sum{_1 || 0} # => 6
- これをワンステップで行うために
#sum
ではnil
の要素をスキップする、またはArray#filter_sum
のようなメソッドを追加する提案です - 最終的には
a.sum{_1 || 0}
で問題ないということで Reject されています
Vim の oldfiles の最大数を変更する
Twitter でいろいろとやり取りしていたので覚書。
Vim の oldfiles の最大数を変更する
Vim oldfiles
の保存数は 'viminfo'
の '
の設定に依存しています。
この保存数を変更する場合は vimrc に以下のような設定を追加することで変更することができます。
" v:oldfiles で保存するファイル数を設定 " vimrc に書いておく必要がある set viminfo+='10000 set viminfo-='100 " デフォルトの設定を削除しておかないt反映されない
また、oldfiles
で保存するファイルパスは Vim を再起動しないと反映されないので注意する必要があります。
ちなみに denite-file/old
はこの oldfiles
のデータを参照しているのでこの設定をすることで denite-file/old
の保存数を変更することもできます。
2021/02/04 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Bug #10593] Emoji is been considered as comment
#️⃣
絵文字以降がコメントアウトとして扱われてしまうというバグ報告
#️⃣ これはコメントアウトです #️⃣ Ruby のコードは実行されません puts "hello, world" #️⃣ コメント
- これは
#️⃣
が囲み文字と呼ばれている絵文字で# + 特殊なコードポイント
で表現されているためです#️⃣
のバイト列がU+0023 + U+FE0F + U+20E3
となる- 参照:めくるめくEmojiの世界/emoji-world
- 実際に字句解析するとこんな感じ
require "ripper" # コメントとして字句解析される p Ripper.lex("#️⃣") # => [[[1, 0], :on_comment, "#️⃣", BEG]] # こっちは普通の文字になる p Ripper.lex("😀") # => [[[1, 0], :on_ident, "😀", CMDARG]]
- これは仕様ということで閉じられている
[Bug #7877] E::Lazy#with_index should be lazy
Enumerable::Lazy#with_index
が Ruby 2.6 と 2.7 で挙動が変わっていたので調べていたらこのチケットを見つけました
# Ruby 2.6 だとブロックの中身が呼ばれるが、2.7 だと呼ばれない pp [1, 2, 3].lazy.with_index { |it, i| l} # 2.6 => [1, 2, 3] # 2.7 => #<Enumerator::Lazy: ...>
- チケット自体は
Enumerable::Lazy#with_index
もlazy
化してほしいというバグチケット- 内容的には
feature
っぽいが
- 内容的には
- この影響で既存の
Enumerable::Lazy#with_index
の挙動がぶっ壊れてしまったぽい - 上記のコードを Ruby 2.7 でも同じようにどうさせる場合は
#force
か#each
するのが無難かなあ
pp [1, 2, 3].lazy.with_index { |it, i| p [it, i] }.force pp [1, 2, 3].lazy.with_index.each { |it, i| p [it, i] }
- ちなみにこの変更、Ruby 2.7 の
NEWS
にも載ってなくてチケット調べるのに手間取りましたgit blame
で該当するコミットを調べるなどしていた- commit: https://github.com/ruby/ruby/commit/e94ac03eb0d07af3cbff20194acf479bf8851c39