2. Self-Intro Joker
▸ id: joker1007
▸ Repro inc. CTO
▸ I’m familiar with Ruby/Rails/Fluentd/ECS/Presto.
▸ I ♥ “Jojo’s Bizarre Adventure” so much.
▸ I’m very happy to talk in Sendai (Moriocho)
17. Ruby Features: Refinements
▸ Refinements provide a way to extend a class locally
▸ Useful use case. (Safety monkey patching)
Ruby Features: Refinements
19. Hacks 1 Method modifiers
▸ final (https://github.com/joker1007/finalist)
▸ forbid method override
▸ override (https://github.com/joker1007/overrider)
▸ enforce method has super method
▸ abstract (https://github.com/joker1007/abstriker)
▸ enforce method override
These method modifiers work when Ruby defines class.
It is actually runtime, but in most case, before main logic.
Hacks: method modifiers
23. How to implement method modifiers
I use so many hook methods.
`included`, `extended`, `method_added`, and `TracePoint`.
And I use `Ripper`.
In other words, I use the power of many black magics !!
Hacks: method modifiers
30. Usage: Method Hook
▸ Method Hook provides a way to implement declarations (like
protected, private)
▸ With class inheritance, Method Hook seems magic !!
▸ But it is not enough to implement “finalist” actually
▸ Ruby has so many cases of method definition
▸ `def` or `define_method`
▸ `include` module
▸ `extend`, `prepend
▸ Each case calls different hooks
Ruby Features: method hooks
31. “include” does not add method
▸ Because `include` insert module to method lookup
hierarchy, it does not touch method entry table.
Ruby Features: method hooks
32. “include” changes only chain of method discovering
Foo
Object
Bar
Insert module to hierarchy
It is different from to add method
Class#ancestors displays class-module hierarchy
Ruby Features: method hooks
33. Hooks in finalist gem
▸ `method_added` to detect override by subclass
▸ `singleton_method_added` to detect override class
method
▸ `included` to detect override by module include
▸ `extended` to detect override by module extend
35. overrider and abstriker uses TracePoint
▸ `inherited` and `included` to start `TracePoint``
call main logic
call main logic
Tracepoint in overrider, abstriker
37. Why use TracePoint?
▸ In order to verify method existence at the end of class
definition.
▸ Ruby interpreter needs to wait until the end of class
definition to know a method absence.
▸ override and abstract cannot detect violation just when
they are called.
▸ In ruby, The only way to detect the violation is
`TracePoint`.
38. Advanced TracePoint: Detect particular class end
Advanced Tracepoint
:end event cannot trace definition by `Class.new`.
Use :c_return and return_value to detect particular class end
39. Advanced TracePoint: Ripper combination
Advanced Tracepoint
Detect target Sexp node by TracePoint#linen
Sexp node type expresses the style of method cal
40. Ripper empowers TracePoint
▸ `Ripper.sexp` returns token position
▸ And TracePoint returns lineno an event occurs.
▸ Ripper provides more information about event context.
▸ ex. power_assert gem is implemented by this combination.
Advanced Tracepoint
41. Hacks 2 (binding_ninja)
▸ https://github.com/joker1007/binding_ninja
▸ It is method wrapper to pass binding of method caller
implicitly.
▸ I talked about it at Rubykaigi 2017 LT.
Hacks: binding_ninja
43. How To Implement
▸ Binding is based on Ruby level control frame.
▸ When Ruby calls c-method, Ruby does not change Ruby level control
frame.
▸ It is core logic of binding_ninja
create binding in C level, binding keeps caller stack in Ruby level
Hacks: binding_ninja
44. Use case: binding_ninja
▸ I implement scala like
implicit parameter in Ruby.
▸ https://github.com/
joker1007/
implicit_parameter
Hacks: binding_ninja
46. Black Magic is dangerous actually,
but it is very fun,
and it extends Ruby potential
These gems is for proof of concept.
But these work and decent
practical.
48. RUBY QUIZ
class Foo
def foo
class Foo
def foo
class Bar < Foo
def foo
class Bar < Foo
def foo
Bar.new.foo()
49. RUBY QUIZ
class Foo
def foo
class Foo
def foo
class Bar < Foo
def foo
class Bar < Foo
Bar.new.foo()
undef_method(:foo)
NoMethodError
remove_method(:foo)
50. Hack: with_resources
Add "with" Statement in Ruby
▸ Safe resource allocate/release
▸ Ensure to release resources
▸ after a lexical scope
▸ in reverse order of allocation
▸ Idioms used very frequently
▸ Other languages:
▸ Java: try-with-resources
▸ Python: with
▸ C#: using
51. Hack: with_resources
Safe Resource Allocation/Release Statement in Ruby
▸ Open method with blocks
▸ File.open(path){|f| ... }
▸ Ruby way (?)
▸ More indentation
▸ Not implemented sometimes
(e.g., TCPSocket)
▸ Simple begin-ensure
▸ Anomaly cases can't be
handled
52. Hack: with_resources
with_resources.gem
▸ Safe resource allocation
▸ Top level "with"
▸ via Kernel refinement
▸ Resource allocation as lambda
▸ Multi statements to allocate resources
▸ to release first resource
if second resource allocation raises exception
▸ Block to define scope for resources
https://github.com/tagomoris/with_resources
53. Hack: with_resources
Implementing "with" in Ruby
▸ TracePoint
▸ "b_return": pass allocated resources to block arguments
▸ "line": identify allocated resources in lambda
▸ Binding
▸ detect newly defined
local variables
in allocation lambda
▸ Refinements
▸ introduce "with"
in top-level without side effects
https://github.com/tagomoris/with_resources
54. Hack: deferral
Alternative: defer?
▸ Multi-step resource
allocation in a method
▸ using "with"
▸ Nesting!
▸ not so bad
▸ many nesting looks
a bit messy :(
▸ Alternative?
▸ "defer" in golang
55. Hack: deferral
Making "defer" in Ruby
▸ Block based "defer"
▸ Should work
▸ Requires 1-level
nesting *always*
▸ Defer.start, end (+ nesting)
look too much (than golang)
▸ Abnormal case:
reassigning variables
56. Hack: deferral
deferral.gem
▸ Safe resource release
▸ Top level "defer"
▸ via Kernel refinements
▸ Deferred processing to release resources
▸ at the end of scope (method, block)
▸ or exception raised
57. Hack: deferral
Implementing "defer" in Ruby
▸ #defer
▸ Enable TracePoint if not yet
▸ Initialize internal stack frame
▸ TracePoint
▸ Monitor method call stack
▸ Get the snapshot of local variables in defer block
▸ Call release blocks at the end of scope
▸ Binding
▸ save/restore local variables of release block
▸ Refinements
▸ introduce "defer" in top-level without side effects
stack level 0
stack level 1