マニアックな
Ruby 2.7新機能紹介
遠藤 侑介
鹿児島Ruby会議01
1
この発表では
• (基調講演らしく)Ruby開発の最新動向を紹介し、
• Ruby 2.7で導入されたりされなかったりする新機能や非互換を
• おかしな使い方 興味深いユースケース
• あやしい仕様 言語設計における細やかな配慮
• 仕様検討の楽屋裏 新機能導入の背景や開発秘話
• を交えながら紹介していきます
• 興味もったら検索→"Ruby Hack Challenge" イベント
2
自己紹介:遠藤侑介 (@mametter)
• クックパッドで働く
フルタイムRubyコミッタ
• テスト、CIの番人
• Ruby3x3ベンチマーク作成
• Ruby 3の静的解析
• クックパッドに興味あったら
お声がけください
3
お品書き
• Rubyの最新開発体制の紹介
• Ruby 2.7新機能のマニアックな紹介
• まとめ
4
お品書き
• ➔Rubyの最新開発体制紹介
• Ruby 2.7新機能のマニアックな紹介
• まとめ
5
プログラミング言語 Ruby
• 日本発のプログラミング言語(1993~)
• まつもとゆきひろ氏(matz)が設計・開発している
• Ruby on Railsというフレームワークが有名で、
Webアプリの市場を(たぶんまだ)獲得している
• 処理系本体はほぼC言語で書かれている
• 最近Rubyで書くところが徐々に増えている
• 添付ライブラリはフルRubyのものも多数
• https://github.com/ruby/ruby
Rubyの開発コミュニティ
• "Ruby core team"
• アクティブな開発者たちを漠然と指す謎ワード
• Ruby界隈では開発者を「コミッタ」と呼ぶ
• アクティブな開発者は10~30人程度
• 1回でもコミットしたことのある人の数:約100人
• 直近1年間で100コミット以上:約10人
• 直近1年間で10コミット以上:約20人
• 直近1年間で1コミット以上:約35人
2019年のコミット数グラフ(雰囲気)
8
Ruby開発の日常
• 年1回、クリスマスごろリリースする
• 次はRuby 2.7
• バグ報告や新機能の議論はバグトラッカでやる
• https://bugs.ruby-lang.org
• 言語の機能提案はmatzが最終決定権を持つ
(『優しい独裁者』)
• 議論促進のため、月1回程度、開発者会議を行う
お品書き
• Rubyの最新開発体制の紹介
• ➔Ruby 2.7新機能のマニアックな紹介
• まとめ
10
パターンマッチ
• Ruby 2.7最大の目玉新機能!!!
• 関数型プログラミング言語でよくあるやつ
11
ary = [1, 2, 3]
case ary
in [0, y, z]
# マッチしない
in [1, y, z]
p y #=> 2
p z #=> 3
end
パターンマッチ
• 従来からあるcase/when分岐で書くと……
12
ary = [1, 2, 3]
case ary[0]
when 0
# マッチしない
when 1
p ary[1] #=> 2
p ary[2] #=> 3
end
パターンマッチ
13
• 何が嬉しいの?
パターンマッチの嬉しさ
• 短くてわかりやすい例を示すのが難しい…
• jsonを分解するのに便利?(実事例はまだない)
• Railsのルーティングの記述に便利?(実事例はまだない)
• 赤黒木を実装するのに便利!(実事例ではない)
• 型プロファイラを実装するのに便利!(実事例ではない)
• パターンマッチは「記号処理」向きの言語機能
• Rubyで記号処理をやる人はあんまいない
• (ポジティブに言えば)Rubyの適用範囲が広がる
14
記号処理とは
• 数値ではなく記号を扱う処理
• アプリケーションで言えば言語処理系とか数式処理
• 今回は『SKIコンビネータ計算』で解説
15
SKIコンビネータ計算
• 型無しラムダ計算を単純化した計算モデル
あるルールで木を繰り返し簡単にしていく遊び
16
K SIK S K I K
ルール (1) と (2)
17
K
x
y
x
I
x
x
ルール (3)
※どれにもマッチしないときは左のサブツリーを見る
18
S
x
y
z
x z y z
Rubyの配列で木を表現する
19
x y
[x, y]
IK
[:K, :I] K SIK S K I K
[[[:K,:I],[:S,:K]], [[:K,:S],[:I,:K]]][[[:K,:I],[:S,:K]],[[:K,:S],[:I,:K]]]
変形ルールをプログラムで書く
20
I
x
x
def ski(e)
if e[0] == :I
e[1] # xの部分
else
...
end
end
[:I, x] x
変形ルールをプログラムで書く
21
K
x
y
x
def ski(e)
if e[0] == :I
e[1]
elsif e[0][0] == :K
e[0][1] # xの部分
else
...
end
endx
[[:K, x], y]
変形ルールをプログラムで書く
22
S
x
y
z
x z y z
def ski(e)
if e[0] == :I
e[1]
elsif e[0][0] == :K
e[0][1]
elsif e[0][0][0]==:S
[[e[0][0][1], # x
e[1]], # z
[e[0][1], # y
e[1]]] # z
else
...
end
end
[[[:S, x], y], z] [[x,z], [y,z]]
23
• ややこしい!
変形ルールをパターンマッチで書く
24
I
x
x
def ski(e)
case e
in [:I, x]
x
else
...
end
end
[:I, x] x
変形ルールをパターンマッチで書く
25
K
x
y
x
def ski(e)
case e
in [:I, x]
x
in [[:K, x], y]
x
else
...
end
endx
[[:K, x], y]
変形ルールをパターンマッチで書く
26
S
x
y
z
x z y z
def ski(e)
case e
in [:I, x]
x
in [[:K, x], y]
x
in [[[:S,x],y],z]
[[x,z],[y,z]]
else
...
end
end[[[:S, x], y], z] [[x,z], [y,z]]
27
• かんたん!
パターンマッチ:結論
28
• パターンにマッチさせるプログラムを書くときに
パターンマッチはとても便利です
• 参考文献
• パターンマッチを深く知りたいなら
➔『n月刊ラムダノート Vol.1 No.3』
• 記号処理に興味を持ったら
➔『RubyでつくるRuby』(自著)
キーワード引数の分離
• Ruby 2.7最大の目玉非互換!
• 正確にはRuby 3.0に予定されている非互換
• 前提知識:ハッシュオブジェクト
• キーと値の対応を表すデータ構造
• 他言語ではマップ、辞書とも
29
{ a: 1, b: 2 }
キー 値
a 1
b 2
キーワード引数の理想と現実
• 理想(Ruby 3.0予定)
• 現実(Ruby 2.X)
30
def foo(a,b,c,x:42)
...
end
foo(1, 2, x: 43)
メソッド
呼び出し
普通の引数に1と2を渡してね
キーワード引数xは43でお願い
あっ、引数cの分が足りないよ!
見直してね
1, 2, {:x=>43}の3引数を渡せ
def foo(a,b,c,x:42)
...
end
a=1、b=2、c={:x=>43}で
xは未指定な➔あとでクラッシュ
foo(1, 2, x: 43)
Ruby 2におけるキーワード引数
• キーワード引数は、普通の引数の一部
• 一番最後にあるハッシュオブジェクト
• どちらも同じ意味 →
• Ruby 2.0設計時はこれでいいと思っていた
• しかし直感に合わないというバグ報告が次々と来た
• 直感にあわせるためにad-hocな仕様変更が繰り返された
• 今では完全なカオスになってしまった
31
foo(x: 43) foo({x: 43})
Ruby 2.6のカオスな挙動の例
• x には何が渡るか?
• 答え:何もわたらない(デフォルト式のnilになる)
• 理由
32
def foo(x=nil, **y)
p x
end
foo({}, **{})
def foo(x=nil, **kw)
p x #=> nil
end
foo({}, **{}) foo({})
kw={}
x=デフォルト
は、無と
同じ意味が自然
**{} 最後のハッシュは
キーワード引数
Ruby 3におけるキーワード引数
• キーワード引数は、普通の引数とは全くの別物
• 混同が起きなくなったのでめでたしめでたし
33
foo(x: 43) foo({x: 43})
キーワード引数を渡す 普通のハッシュ引数を渡す
…非互換!
• 意図的に混同してたコードがRuby 3で動かない
34
def foo(**opt)
...
end
h = {x: 43}
foo(h)
def foo(opt={})
...
end
foo(x: 43)
このケースは
Ruby 3でも
許すことになった
これは
許さない
と書き直してね
foo(**h)
Ruby 2.7は移行支援バージョン
• 基本的にはRuby 2.6と同じ意味
• しかし3.0で変わる挙動に警告を出す
• www.ruby-lang.orgに移行ガイドを掲載する予定です
35
def foo(a,b,c,opt: 42)
end
foo(1, 2, opt:43)
#=> -:3: warning: The keyword argument is passed as the last hash parameter
# -:1: warning: for `foo' defined here
委譲の新記法: (...)
• 委譲:引数をすべて別メソッドに横流しすること
36
def bar(a, b, c)
end
def foo(...)
bar(...)
end
foo(1, 2, 3)
def foo(*args, &blk)
bar(*args, &blk)
end
Ruby 2でまじめに書く場合
def foo(*args, **opts, &blk)
bar(*args, **opts, &blk)
end
Ruby 3でまじめに書く場合
トラップ:(...) はカッコが必須
• 「Rubyはカッコが省略できていいよね~」
• Ruby 2.7では行末に…があったら警告が出ます
37
def foo(...)
bar ...
end
残念!endless range
と解釈されます
(bar...)
def foo(...)
bar ...
end
#=> -:2: warning: ... at EOL, should be parenthesized?
numbered parameter
• 引数名を付けたくない勢のための福音 (?)
38
ary.map {|x| x.to_s(16) }
ary.map { _1.to_s(16) }
引数を書くのが
イヤ!
numbered parameter:背景
• 簡単 (?) な書き方がある
• ちょっと複雑になると簡単 (?) にできなかった
• &:to_s を魔拡張する提案が繰り返された
39
ary.map {|x| x.to_s } ary.map(&:to_s)
ary.map {|x| x.to_s(16) } ×
ary.map {|x| JSON.parse(x) } ×
ary.map { _1.to_s(16) }
ary.map { JSON.parse(_1) }
ary.map(&:to_s << 16) ary.map(&.to_s(16)) ary.map.as_self{to_s(16)}
ary.map(&(:to_s.proc >> :ord.to_proc)) ary.map(&(&:to_s >> &:ord))
numbered parameter: 複数引数の罠
• _2を読み出すだけで_1の意味が変わる
• こういう意味になってる
40
h = { 1 => 2 }
h.map { p _1 } #=> [1, 2]
h.map { x = _2; p _1 } #=> 1
h = { 1 => 2 }
h.map {|a| p a } #=> [1, 2]
h.map {|k, v| p k } #=> 1
numbered parameter: 書けない場所
• 入れ子のブロックでは1回しか書けない
41
1: n.times {
2: _1.times {
3: _1
4: }
5: }
-:3: numbered parameter is already used in
-:2: outer block here
こういう
ややこしいのは
書いてほしくない
numbered parameter: 結論
• 名前を書きましょう
42
余談:決まるまでの長大な議論
• 2019/01: $_, @0,@1,@2, {|x|なのか {|x,|なのか
• 2019/02: nobuが@1でパッチ書いてみることに
• 2019/03: @1入りでpreview1リリースの方向
• 2019/04: eregonがブロック引数の利用例統計
• 2019/06: @, @0, it, $it, ¥it, _, 複引数必要か、{|x,|
• 2019/07: it, _, %0, @, @1,@2, %1,%2, :1, :2
• 2019/09: _0 / _1,_2,_3の方向で固まる
• 2019/10: _0 が消える
• 開発者会議の議事録は公開されています(バグトラッカから)
43
令和対応
• Ruby 2.7は令和に対応
• 正確にはRuby 2.6.3から
• 言語処理系が令和に対応するとは??
44
令和対応:Date.jisx0301
• 2019/04/01 令和発表
• 2019/04/17 Ruby 2.6.3リリース(投機実行)
• 2019/05/01 令和施行
• 2019/05/20 JIS X 0301改正
45
$ ruby -rdate -e 'puts Date.new(2019, 4, 30).jisx0301'
H31.04.30
$ ruby -rdate -e 'puts Date.new(2019, 5, 1).jisx0301'
R01.05.01
令和対応:Unicode ¥u32FF (㋿)
• 2019/04/01 令和発表
• 2019/04/?? Unicode 12.1.0 beta
• 2019/04/17 Ruby 2.6.3リリース(beta採用)
• 2019/05/01 令和施行
• 2019/05/07 Unicode 12.1.0リリース
46
$ ruby -e 'puts "㍻".unicode_normalize(:nfkd)'
平成
$ ruby -e 'puts "㋿".unicode_normalize(:nfkd)'
令和
入らない機能:pipeline operator
• Elixirから輸入する予定だった演算子
47
|>
pipeline operatorの意味
• F#での意味
• Elixirでの意味
• Rubyで導入予定だった意味
48
x |> foo(1) foo(x, 1)=
x |> foo(1) x.foo(1)=
x |> foo 1 foo 1 x=
pipeline operatorの本来の目的
• 関数名を処理順に書けるようにすること
• F#
• Elixir
• 非オブジェクト指向言語でメソッドチェーンっぽく
書くためのハックだった
• Rubyではメソッド呼び出しの別記法とするのは自然
49
x |> foo 1 |> bar 2 bar 2 (foo 1 x)=
bar(foo(x, 1), 2)x |> foo(1) |> bar(2) =
pipeline operatorの「誤解」
• 本来の目的が忘れられ、
「xをfoo(1)の第1引数にする構文」と思う人多数
• Rubyの意味はそれと違うので、
「思ってたんと違う!」という苦情が殺到した
• 結果、取りやめになった
50
x |> foo(1) foo(x, 1)=
Rubyが狙っていたこと
• Range#eachのカッコを省略したかった
• メソッドチェーンに
コメントを書きたかった
• プログラミング言語は
見た目が9割!
51
x
# fooをやる
|> foo 1
# barをやる
|> bar 2
1..10 |> each {|x| ... }
x
# fooをやる
.foo 1
# barをやる
.bar 2
Ruby 2.7では
これが書ける
入らない機能:メソッド取出演算子
52
.:
メソッド取出演算子の意味
• メソッドオブジェクトを取り出す
• と同じ意味
53
"str".method(:upcase)
m = "str".:upcase #=> #<Method: String#upcase>
m.call #=> "STR"
LISP-1とLISP-2
• Python (LISP-1)
• 括弧無→メソッド取出
• 括弧付→メソッド呼出
• 一貫している
•Ruby (LISP-2)
• 括弧ありでも無しでも
メソッド呼び出し
• メソッド取出が面倒
• 取出してから呼出も面倒
54
"str".upper() #=> "STR"
"str".upper
#=> <built-in method upper
of str object at…>
"str".upcase #=> "STR"
"str".method(:upcase)
#=> #<Method: String#upcase>
"str".:upcase.call
"str".:upcase
#=> #<Method: String#upcase>
ユースケース
• methodメソッドが再定義されても大丈夫
• デバッグに便利
• 関数型プログラミング?
55
p obj.:foo.source_location #=> [ファイル名, 行番号]
[json1, json2].map(&JSON.:parse)
[json1, json2].map {|x| JSON.parse(x) }
消された理由
• 「関数型プログラミング?」の用途には不完全
• ↓のケースは .: で解決しない
• 今後も &:to_s を魔拡張しつづけるのか……?
• Rubyの関数型プログラミングのgrand planを
(誰かが)考えてから再挑戦することに
56
[json1, json2].map {|x| JSON.parse(x, symbolize_names: true) }
2.7は他にも新機能や改善や非互換がたくさん
• IRBの刷新
• $SAFE消滅
• filter_map
• Enumerable#tally
• GC.compact
• Time#floor, #ceil
57
• beginless range
• self.private_method
• Array#intersection
• Comparable#clamp with range
• CESU-8
• Enumerator.produce
• Enumerator::Lazy#eager
• Enumerator::Yielder#to_proc
• Fiber#raise
• FrozenError#receiver
• IO#set_encoding_by_bom
• Integer#[] with range
• Method#inspect
• Module#const_source_location
• 一部のto_sがfrozen
• ObjectSpace::WeakMap#[]=
• Regexp#match?(nil)
• RubyVM.resolve_feature_pathの移動
• UnboundMethod#bind_call
• Bundler更新
• CGI.escapeHTML高速化
• CSV更新
• Net::FTP改良
• Net::IMAP改良
• open-uri改良
• OptionParser did_you_mean
• Racc 更新
• REXML更新
• RSS更新
• RubyGems更新
• StringScanner更新
• 一部の標準ライブラリがgem化
• ほか
クックパッド開発者ブログで網羅解説予定 w/ ko1
お品書き
• Rubyの最新開発体制の紹介
• Ruby 2.7新機能のマニアックな紹介
• ➔まとめ
58
まとめ
• Ruby 2.7にご期待ください
• 数多くの新機能や改良
• Ruby 3を見据えた準備
• 意外といろいろ考えてやってます
• あなたもRuby開発に参加できます
• メーリングリストやバグトラッカをウォッチ
• もしくは、検索:"Ruby Hack Challenge"
59

マニアックなRuby 2.7新機能紹介