Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
†Ruby黒魔術経典†
Tomohiro Hashidate
June 08, 2019
Programming
13
5.1k
†Ruby黒魔術経典†
名古屋Ruby会議04 発表資料
Tomohiro Hashidate
June 08, 2019
Tweet
Share
More Decks by Tomohiro Hashidate
See All by Tomohiro Hashidate
ReproのImport/Exportを支えるサーバーレスアーキテクチャ
joker1007
1
680
Ruby on Rails on Lambda
joker1007
13
6.7k
Sidekiq to Kafka ストリームベースのmicro services
joker1007
3
7.7k
令和時代のRails運用
joker1007
33
12k
TracePointから学ぶRubyVM
joker1007
0
1.1k
What a cool Ruby-2.7 is !
joker1007
2
430
How to extend TracePoint
joker1007
2
180
Cassandraの活用事例とパフォーマンス特性
joker1007
3
670
Pragmatic Monadic Programming in Ruby
joker1007
4
10k
Other Decks in Programming
See All in Programming
byte列のbit表現を得るencodingライブラリ作った
convto
1
210
Licences open source : entre guerre de clochers et radicalité
pylapp
2
370
About Type Syntax Proposal
quramy
1
1.2k
脱オブジェクト指向講座(5分LT資料)
kishida
8
11k
あなたの会社の古いシステム、なんとかしませんか?~システム刷新から考えるDX化への道筋とバリエーション~/webinar20220420-systems
grapecity_dev
0
130
TechFeed Conference 2022 - Kotlin Experimental
jmatsu
0
860
Android入門
hn410
0
310
書籍『良いコード/悪いコードで学ぶ設計入門』でエンジニアリングの当たり前を変える
minodriven
3
1.2k
機能横断型チームにおける技術改善
takeshiakutsu
3
510
Quartoを使ってみませんか / quarto_get_started
s_uryu
2
420
テスト設計技法をなぜ&どのように使うのか体験しよう!
imtnd
0
540
CIでAndroidUIテストの様子を録画してみた
mkeeda
0
190
Featured
See All Featured
Building Your Own Lightsaber
phodgson
94
4.6k
What’s in a name? Adding method to the madness
productmarketing
11
1.5k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
119
28k
Designing the Hi-DPI Web
ddemaree
272
32k
Put a Button on it: Removing Barriers to Going Fast.
kastner
56
2.3k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
15
920
ParisWeb 2013: Learning to Love: Crash Course in Emotional UX Design
dotmariusz
100
5.9k
What the flash - Photography Introduction
edds
61
10k
Creatively Recalculating Your Daily Design Routine
revolveconf
207
10k
Visualization
eitanlees
124
11k
Become a Pro
speakerdeck
PRO
3
790
Dealing with People You Can't Stand - Big Design 2015
cassininazir
350
21k
Transcript
†Ruby 黒魔術経典† @joker1007 (Repro inc. CTO) 名古屋Ruby 会議04
self.inspect @joker1007 Repro inc. CTO Data Engineering, Architect Ruby で悪事を働く⼈と⾒做されている
We provide a web service as ... Analytics of Mobile
Apps, Web Apps. Marketing Automation. We're hiring!!
https://youtu.be/1mauqP9zWbM?t=31
⼤いなる⼒には ⼤いなる責任が 伴う
メタプログラミングや魔術的な挙動をする コードは強⼒だが 侵してはならないこともある
今⽇の議題 以下の内容について話す。 黒魔術において守るべきルール 実際に魔術を編み出すために使えるパーツ 考え⽅の具体例
Part1 守りの黒魔術
守りの黒魔術 その1 組込みクラスを壊さない 特に require しただけで挙動を変えない 上書きさせたい場合は、必ずユーザーに指定させる 組込みのクラス・メソッドは全てのRuby ユーザーが期待している挙動が ある。(
当たり前の話) 暗黙的に弄って挙動を変えたら、何が起こるのか分からなくなる。
守りの黒魔術 その2 スタックが追える様にすること eval を使う場合に定義場所のロケーションを適切に設定する。 でないと例外が発⽣した時にどこで定義されたコードなのか分からない。 実はRuby の例外のバックトレースは⾃分で上書きできるので、ノイズに なりそうな情報を隠すこともできる。
守りの黒魔術 その3 パフォーマンスを意識すること eval やbinding は負荷が⾼い。 呼び出し回数を少なくするために以下の様な⽅法が使える。 クラスレベルでソースコードをキャッシュする クラス定義時や初回実⾏時のみ動作する様にする メソッド定義にだけ利⽤する
初回実⾏時にメソッドを上書きする
守りの黒魔術 その4 TracePoint の利⽤は明⽰的に そして、絶対にensure でdisable すること。 途中で例外が発⽣するとtrace が有効のままになる。 trace
が有効のままになるとマジで何が起きるか分からない。 フックが暴⾛してstack level too deep になるのはまだ良い⽅。
Part2 黒魔術に使えるAPI の探し⽅
基本はリファレンスをひたすら読むこと それだけだと雑過ぎるので、もうちょい解説する。
るりまのここを読むべし BasicObject Object Module/Class Method Proc Kernel (ObjectSpace)
⼀回読んでも忘れるから、 とりあえずこの辺りを読み返す癖を付けておく。 ちなみに、 RubyVM::AbstractSyntaxTree はまだるりまが無いです。 ( プルリクチャンス)
使えそうな機能の例 評価コンテキスト操作 eval 系 トリガー/ フック メソッドフック, TracePoint, included, inherited,
method_missing, trace_var, trap, finalizer ⼤域脱出 throw/catch, Fiber
使えそうな機能の例 ( 続き) オブジェクト参照 _variable_get 系, const_get, ObjectSpace 変数/ 定数操作
_variable_set 系, const_set メタデータ取得 Method やProc から取れる情報 メソッドの動的定義 define_method, module_eval
メタプログラミングパターン メソッド定義 DSL 動的解析 静的解析 ⾃動的/ 暗黙的処理の追加 ⾔語拡張 ↓ に向かう程魔術度が増す
Part3 考え⽅の具体例 暗黙のブロック引数 it を作ってみよう see. https://bugs.ruby-lang.org/issues/15897
proc の中で暗黙の内に it でパラメータを参照できるとは、 Ruby の動作に置き換えるとどういうことかを考えてみる。 評価コンテキスト内で it というローカル変数に値が⼊っている proc
のself に it というメソッドが定義されていて、パラメータを取 得できる proc の外側で it が定義されている。 ローカル変数 or 引数 これらのどれかが実現できれば良さそう。
ローカル変数追加⽅式について検討 local_variable_set が使えそう local_variable_set は変数書き換えは簡単だが、新規に追加するのは難 しい binding が毎回新しく⽣成されるため binding を固定してeval
しなければならない そもそもブロック呼び出しに実際に使われている値をどうやって事前 に取得する? なんか無理っぽい
メソッド追加⽅式について検討 評価コンテキストにおけるself は取得できる Proc#binding やTracePoint で可能 単純にメソッドを定義するとあるクラスのインスタンス全てが影響す る 特異メソッドとして定義すれば可能かも しかしスコープを抜けた後も参照できてしまう
Refinements は使えないか ブロックの定義が別の場所なのでeval が必要 ローカル変数⽅式と同様の問題がある
外側でit を定義する⽅法について検討 新しいproc でラップして追加できる ブロックに渡される引数を事前に知る必要が無い やはりeval する必要がある
結論: 恐らくeval が必須 そしてproc でwrap ⽅式が現実的 eval するためにはソースコード断⽚が必要
ブロックのソースコードを取る⽅法 RubyVM::AST.of or parser gem で位置を特定し読む ( またお前か)
特定のメソッドを対象にPoC を書く module Ext def map(*args, &block) source = File.readlines(block.source_location[0])
proc_binding = block.binding ast = RubyVM::AbstractSyntaxTree.of(block) args_tbl = ast.children[0] block_node = ast.children[2] if args_tbl.empty? extracted = extract_source( source, block_node.first_lineno, block_node.first_column, block_node.last_lineno, block_node.last_column) new_block = proc_binding.eval("proc { |it| #{extracted} }") super(*args, &new_block) else super(*args, &block) end end end Array.prepend(Ext) n = 3 [1, 2, 3].map { p it + n } # => [4, 5, 6]
出来た! 後はgem にするだけ
こんな感じで、⾃分の場合はゴールから逆算して考える。 やりたい事が出来るとはRuby においてオブジェクトの状態や変数のスコ ープ、メソッドの定義がどうなっていればいいかを想像し、そこに⾄る⽅ 法を逆向きに辿って実現可能な⽅法を考える。
ちなみに、実はこれ RubyKaigi2019 で話したものと同じ パターンを使っている
最後に 黒魔術を使うためにはRuby の挙動や各オブジェクトが何なのかというこ とを詳しく知る必要がある。 魔術的な挙動を起こす⽅法を知ることは、安全なコードの書き⽅を知るこ とにも繋がる。 いざという時の選択肢も増える。 Ruby より深く楽しみ、より良いコードに繋げよう