Use case of Refinements With Black Magic

Use case of Refinements With Black Magic

Rubykaigi 2017 LT

Introduce use case refinements and binding_ninja gem


Tomohiro Hashidate

September 19, 2017


  1. Use case of Re nements With Black Magic Tomohiro Hashidate

  2. Sorry, I talk in Japanese very fastly I will upload

    this slides later
  3. self.inspect @joker1007 Repro inc. CTO Vimmer Ruby/Rails uentd/embulk Docker/ECS Bigquery/EMR/Hive/Presto

  4. I am one of authors of パーフェクトRuby, Please check it!

  5. I Re nements because.. Allow override builtin Classes Romantic

  6. Introduce some use cases of Re nements

  7. Case1 MonkeyPatch to google-api-client In past, google-api-client could not skip_serialization

    . It is not ef ciently in a case that response data size is enormous, especially Bigquery. Re nements enable to replace serialization implementation in speci c context. (It is already unnecessary now)
  8. module QueryEngine::Bigquery class SimpleHashRepresentable def initialize(instance = {}) @instance =

    instance end def from_json(body, options) @instance.merge!(Oj.load(body)) end end module HashrizeGetJobQueryResults refine Google::Apis::BigqueryV2::BigqueryService do def get_job_query_results(*args) command = make_simple_command(:get, 'projects/{projectId command.response_representation = SimpleHashRepresentable command.response_class = Hash # Omit end end end end
  9. Case2 Concern Module We want to send data from application

    to uentd. That data consists of various objects. I want to implement some helper methods in those classes. Re nements can implement helper methods that is called in concern module context only.
  10. module FluentLoggable using( do refine Clip do def fluentd_tag "fluentd.tag.definition"

    end def fluentd_payload # constructing payload end def fluentd_timestamp created_at end # Other helpers end end) def post_to_fluentd # Publish to external context Fluent::Logger.post_with_time( fluentd_tag, fluentd_payload, fluentd_timestamp) end end
  11. Case3 DSL I implemented this. def foo 1 | 2

    | 3 5 | 8 | 13 0 | 0 | 0 end foo #=> Like [[1,2,3], [5,8,13], [0,0,0]] objects By Re nements How?
  12. binding_ninja this gem passes binding of method caller implicity Lightweight

    alternative of binding_of_caller class Foo extend BindingNinja def foo(binding, arg) p binding p arg end auto_inject_binding :foo end # => <Binding of toplevel> # => 1
  13. binding_ninja impl static VALUE auto_inject_binding_invoke(int argc,VALUE *argv,VALUE self) { VALUE

    binding, args_ary; binding = rb_binding_new(); // Important args_ary = rb_ary_new_from_values(argc, argv); rb_ary_unshift(args_ary, binding); return rb_call_super(argc+1, RARRAY_CONST_PTR(args_ary)); } Create binding in C method call, returns caller context binding. Because Ruby level cfp is not changed.
  14. And create and prepend module in C method call to

    wrap any methods. mod_name = rb_mod_name(mod); extensions = rb_ivar_get(rb_mBindingNinja, rb_intern("@auto_inject_binding_extensions")); ext_mod = rb_hash_aref(extensions, mod_name); if (ext_mod == Qnil) { ext_mod = rb_module_new(); rb_hash_aset(extensions, mod_name, ext_mod); } if (rb_mod_include_p(mod, ext_mod) == Qfalse) { rb_prepend_module(mod, ext_mod); } rb_define_method_id(ext_mod, SYM2ID(method_sym), auto_inject_binding_invoke, -1);
  15. Compare to binding_of_caller Warming up -------------------------------------- plain 343.111k i/100ms binding_ninja

    150.293k i/100ms binding_of_caller 7.002k i/100ms Calculating ------------------------------------- plain 7.046M (± 0.2%) i/s - 35.340M in binding_ninja 2.177M (± 0.4%) i/s - 10.971M in binding_of_caller 71.916k (± 1.1%) i/s - 364.104k in Comparison: plain: 7045866.1 i/s binding_ninja: 2176528.7 i/s - 3.24x slower binding_of_caller: 71916.4 i/s - 97.97x slower 30x faster and very simple code, but restricted. binding_ninja get only direct caller binding.
  16. Back to Table Syntax DSL module TableSyntaxImplement extend BindingNinja auto_inject_binding

    def |(b, other) # Wrap method caller = b.receiver # Define instance variable in caller context! if caller.instance_variable_defined?(:@__table) table = caller.instance_variable_get(:@__table) else table = caller.instance_variable_set(:@__table, table) end row = table.add_row(row); row.add_param(other) table end end And refine Builtin classes. String , Integer , Nil etc.
  17. This DSL is used by rspec-parameterized acutually

  18. describe "plus" do using RSpec::Parameterized::TableSyntax where(:a, :b, :answer) do 1

    | 2 | 3 5 | 8 | 13 0 | 0 | 0 end with_them do it "should do additions" do expect(a + b).to eq answer end end end RSpec ExampleGroup is actual Class de nition. It is Re nements friendly
  19. Re nements is fun Shall we use Re nements? Thanks!!