Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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
Tweet

More Decks by Tomohiro Hashidate

Other Decks in Programming

Transcript

  1. Use case of Re nements
    With Black Magic
    Tomohiro Hashidate (@joker1007)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  6. Introduce some use cases
    of
    Re nements

    View full-size slide

  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)

    View full-size slide

  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

    View full-size slide

  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.

    View full-size slide

  10. module FluentLoggable
    using(Module.new 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

    View full-size slide

  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?

    View full-size slide

  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
    Foo.new.foo(1)
    # =>
    # => 1

    View full-size slide

  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.

    View full-size slide

  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);

    View full-size slide

  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.

    View full-size slide

  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 = Table.new
    caller.instance_variable_set(:@__table, table)
    end
    row = Table::Row.new(self)
    table.add_row(row); row.add_param(other)
    table
    end
    end
    And refine Builtin classes. String , Integer , Nil etc.

    View full-size slide

  17. This DSL is used by
    rspec-parameterized
    acutually

    View full-size slide

  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

    View full-size slide

  19. Re nements is fun
    Shall we use Re nements?
    Thanks!!

    View full-size slide