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

Multiverse Ruby

Multiverse Ruby

Slides from my talk by the same name at RubyKaigi 2023, given in Japanese.

shioyama/im
shioyama/rails_on_im

Chris Salzberg

May 12, 2023
Tweet

More Decks by Chris Salzberg

Other Decks in Programming

Transcript

  1. Multiverse Ruby
    Multiverse Ruby
    Chris Salzberg
    Chris Salzberg

    View Slide

  2. @shioyama

    View Slide

  3. Chris Salzberg
    Staff developer, Ruby and Rails Infra

    View Slide


  4. View Slide

  5. 1.Sharing code in Ruby
    2.Modules as Universes
    3.Isolated Module Autoloader

    View Slide

  6. Sharing code in Ruby

    View Slide

  7. n = 10
    a, b = 0, 1
    n.times do
    a, b = b, a + b
    end
    puts a

    View Slide

  8. def self.fib(n)
    a, b = 0, 1
    n.times do
    a, b = b, a + b
    end
    a
    end

    View Slide

  9. module Fibonacci
    def self.fib(n)
    a, b = 0, 1
    n.times do
    a, b = b, a + b
    end
    a
    end
    end

    View Slide

  10. module Fibonacci
    def self.fib(n)
    a, b = 0, 1
    n.times do
    a, b = b, a + b
    end
    a
    end
    end
    code

    View Slide

  11. module Fibonacci
    def self.fib(n)
    a, b = 0, 1
    n.times do
    a, b = b, a + b
    end
    a
    end
    end
    architecture

    View Slide

  12. In Ruby, we share code
    by sharing architecture.

    View Slide

  13. that architecture is...

    View Slide

  14. global

    View Slide

  15. finite

    View Slide

  16. permanent

    View Slide

  17. Ruby’s architecture is not portable

    View Slide

  18. View Slide

  19. Modules as Universes

    View Slide

  20. Named Anonymous
    Modules

    View Slide

  21. module Foo
    class Bar
    end
    end
    Named

    View Slide

  22. module Foo
    class Bar
    end
    end
    mod = Module.new
    mod.name
    #=> nil
    Named Anonymous

    View Slide

  23. mod = Module.new

    View Slide

  24. mod = Module.new
    mod::Foo = Module.new

    View Slide

  25. mod = Module.new
    mod::Foo = Module.new
    mod::Foo.name

    View Slide

  26. mod = Module.new
    mod::Foo = Module.new
    mod::Foo.name
    #=> "#::Foo"

    View Slide

  27. mod = Module.new
    mod::Foo = Module.new
    mod::Foo.name
    #=> "#::Foo"
    temporary name

    View Slide

  28. // variable.c:138
    static VALUE
    make_temporary_path(VALUE obj, VALUE klass)
    {
    VALUE path;
    switch (klass) {
    case Qnil:
    path = rb_sprintf("#", (void*)obj);
    break;
    case Qfalse:
    path = rb_sprintf("#", (void*)obj);
    break;
    default:
    path = rb_sprintf("#<%"PRIsVALUE":%p>", klass, (void*)obj);
    break;
    }
    OBJ_FREEZE(path);
    return path;
    }

    View Slide

  29. mod::Foo.name
    #=> "#::Foo"
    temporary name

    View Slide

  30. mod::Foo.name
    #=> "#::Foo"
    MyFoo = mod::Foo
    temporary name

    View Slide

  31. mod::Foo.name
    #=> "#::Foo"
    MyFoo = mod::Foo
    mod::Foo.name
    #=> "MyFoo"
    permanent name
    temporary name

    View Slide

  32. Modules
    Named Anonymous

    View Slide

  33. Modules
    temporary

    View Slide

  34. temporary == portable

    View Slide

  35. module Foo
    class Bar
    end
    end

    View Slide

  36. mod = Module.new
    module mod::Foo
    class Bar
    end
    end

    View Slide

  37. mod = Module.new
    module mod::Foo
    class Bar
    end
    end
    mod::Foo::Bar.name
    #=> "#::Foo::Bar"

    View Slide

  38. Object::Foo Object::Baz
    Object::Foo::Bar
    Object::Foo::Qux

    View Slide

  39. Object
    Object::Foo Object::Baz
    Object::Foo::Bar
    Object::Foo::Qux
    Permanent Root

    View Slide

  40. mod

    View Slide

  41. mod::Foo mod::Baz
    mod::Foo::Bar
    mod::Foo::Qux
    mod
    Temporary Root

    View Slide

  42. MyFoo = mod::Foo
    MyFoo::Bar
    MyFoo::Qux
    mod
    mod::Baz

    View Slide

  43. MyGem::Foo MyGem::Baz
    MyGem::Foo::Bar
    MyGem::Foo::Qux
    MyGem = mod

    View Slide

  44. a module is the root of a universe

    View Slide

  45. Isolated Module Autoloader

    View Slide

  46. require "my_gem"

    View Slide

  47. 📦= import "my_gem"

    View Slide

  48. RUBY 3.2
    RUBY 3.2

    View Slide

  49. View Slide

  50. View Slide

  51. View Slide

  52. View Slide

  53. View Slide

  54. module Fibonacci
    end
    fibonacci.rb
    module Fibonacci
    end
    load "fibonacci.rb"

    View Slide

  55. module Fibonacci
    end
    fibonacci.rb module MyWrapper
    end
    load "fibonacci.rb", MyWrapper
    module Fibonacci
    end

    View Slide

  56. module Fibonacci
    end
    fibonacci.rb module
    📦
    end
    load "fibonacci.rb",
    📦
    module Fibonacci
    end

    View Slide

  57. import load
    ⇨ ?

    View Slide

  58. require "my_gem"
    # my_gem.rb
    require "foo"
    require "bar"
    # foo.rb
    # bar.rb
    require
    require
    require
    require
    require

    View Slide

  59. mod = import "my_gem"
    # my_gem.rb
    require "foo"
    require "bar"
    # foo.rb
    # bar.rb
    require
    require
    require
    require
    require
    load
    load
    load
    load
    load
    load
    load

    View Slide

  60. mod = import "my_gem"
    # my_gem.rb
    require "foo"
    require "bar"
    # foo.rb
    # bar.rb
    require
    require
    require
    require
    require
    load
    load
    load
    load
    load
    load
    load

    View Slide

  61. View Slide

  62. autoload

    View Slide

  63. Object.autoload(:MyGem, "lib/my_gem.rb")

    View Slide

  64. Object.autoload(:MyGem, "lib/my_gem.rb")
    mod

    View Slide

  65. im i
    isolated
    solated
    m
    module
    odule
    autoloader
    autoloader
    i
    isolated
    solated
    m
    module
    odule
    autoloader
    autoloader

    View Slide

  66. Im is a Zeitwerk fork

    View Slide

  67. lib/my_gem.rb → MyGem
    lib/my_gem/foo.rb → MyGem::Foo
    lib/my_gem/bar_baz.rb → MyGem::BarBaz
    lib/my_gem/woo/zoo.rb → MyGem::Woo::Zoo
    Zeitwerk: Code Loader

    View Slide

  68. Zeitwerk: Code Loader
    lib/my_gem.rb → MyGem
    lib/my_gem/foo.rb → MyGem::Foo
    lib/my_gem/bar_baz.rb → MyGem::BarBaz
    lib/my_gem/woo/zoo.rb → MyGem::Woo::Zoo
    Object::
    Object::
    Object::
    Object::

    View Slide

  69. lib/my_gem.rb → MyGem
    lib/my_gem/foo.rb → MyGem::Foo
    lib/my_gem/bar_baz.rb → MyGem::BarBaz
    lib/my_gem/woo/zoo.rb → MyGem::Woo::Zoo
    Im: Isolated Code Loader
    mod::
    mod::
    mod::
    mod::
    temporary root

    View Slide

  70. mod = import "my_gem"

    View Slide

  71. mod = import "my_gem"
    mod::MyGem::Foo
    #=> loads "lib/my_gem/foo.rb"

    View Slide

  72. mod = import "my_gem"
    mod::MyGem::Foo
    #=> loads "lib/my_gem/foo.rb"
    mod::MyGem::Foo::BarBaz
    #=> loads "lib/my_gem/foo/bar_baz.rb"

    View Slide

  73. mod = import "my_gem"
    mod::MyGem::Foo
    #=> loads "lib/my_gem/foo.rb"
    mod::MyGem::Foo::BarBaz
    #=> loads "lib/my_gem/foo/bar_baz.rb"
    mod::MyGem::Foo::Woo::Zoo
    #=> loads "lib/my_gem/foo/woo/zoo.rb"

    View Slide

  74. Im is compatible with Zeitwerk

    View Slide

  75. 実装

    View Slide

  76. # lib/zeitwerk/loader/config.rb
    unless real_mod_name(namespace)
    raise Zeitwerk::Error,
    "root namespaces cannot be anonymous"
    end

    View Slide

  77. # lib/zeitwerk/loader/config.rb
    unless real_mod_name(namespace)
    raise Zeitwerk::Error,
    "root namespaces cannot be anonymous"
    end

    View Slide

  78. # lib/zeitwerk/loader.rb
    module Zeitwerk
    class Loader

    View Slide

  79. # lib/im/loader.rb
    module Im
    class Loader < Module
    root of the universe

    View Slide

  80. loader::Foo loader::Baz
    loader::Foo::Bar
    loader::Foo::Qux
    loader

    View Slide

  81. # lib/my_gem.rb (main file)
    require "zeitwerk"
    loader = Zeitwerk::Loader.for_gem
    loader.setup # ready!
    module MyGem
    # ...
    end
    loader.eager_load # optionally

    View Slide

  82. # lib/my_gem.rb (main file)
    require "im"
    loader = Im::Loader.for_gem
    loader.setup # ready!
    module loader::MyGem
    # ...
    end
    loader.eager_load # optionally
    define under loader,
    not at top-level!

    View Slide

  83. def require(path)
    filetype, feature_path =
    $:.resolve_feature_path(path)
    if (loader = Im::Registry.loader_for(path))
    # ...
    $LOADED_FEATURES << feature_path
    begin
    load path, loader
    rescue => e
    $LOADED_FEATURES.delete(feature_path)
    raise e
    end

    View Slide

  84. def require(path)
    filetype, feature_path =
    $:.resolve_feature_path(path)
    if (loader = Im::Registry.loader_for(path))
    # ...
    $LOADED_FEATURES << feature_path
    begin
    load path, loader
    rescue => e
    $LOADED_FEATURES.delete(feature_path)
    raise e
    end

    View Slide

  85. def require(path)
    filetype, feature_path =
    $:.resolve_feature_path(path)
    if (loader = Im::Registry.loader_for(path))
    # ...
    $LOADED_FEATURES << feature_path
    begin
    load path, loader
    rescue => e
    $LOADED_FEATURES.delete(feature_path)
    raise e
    end

    View Slide

  86. def require(path)
    filetype, feature_path =
    $:.resolve_feature_path(path)
    if (loader = Im::Registry.loader_for(path))
    # ...
    $LOADED_FEATURES << feature_path
    begin
    load path, loader
    rescue => e
    $LOADED_FEATURES.delete(feature_path)
    raise e
    end
    🙈

    View Slide

  87. - assert A::X
    - assert B::X
    + assert loader::A::X
    + assert loader::B::X

    View Slide

  88. ...........................................................
    ...........................................................
    ...........................................................
    ...........................................................
    ...........................................................
    ...........................................................
    ....................................
    Finished tests in 2.968770s, 131.3675 tests/s, 319.6610
    assertions/s.
    390 tests, 949 assertions, 0 failures, 0 errors, 0 skips
    🎉

    View Slide

  89. The cpath problem

    View Slide

  90. def cpath(parent, cname)
    "#{parent.name}::#{cname}"
    end

    View Slide

  91. def cpath(parent, cname)
    "#{parent.name}::#{cname}"
    end
    Foo :Bar

    View Slide

  92. def cpath(parent, cname)
    "#{parent.name}::#{cname}"
    end
    Foo :Bar
    #=> "Foo::Bar" cpath

    View Slide

  93. {
    "Foo" ⇨ <#Zeitwerk::Loader>,
    "Foo::Bar" ⇨ <#Zeitwerk::Loader>,
    # ...
    }
    cpath
    Zeitwerk

    View Slide

  94. {
    "<#Im::Loader>::Foo" ⇨ ...
    "<#Im::Loader>::Foo::Bar" ⇨ ...
    # ...
    }
    cpath
    Im

    View Slide

  95. MyFoo = mod::Foo

    View Slide

  96. temporary permanent
    "#<...>::Foo" "MyFoo"
    MyFoo = mod::Foo

    View Slide

  97. {
    "<#Im::Loader>::Foo" ⇨ ...
    "<#Im::Loader>::Foo::Bar" ⇨ ...
    # ...
    }
    cpaths["MyFoo"] #=> nil

    View Slide

  98. assert loader::A
    X = loader::A
    assert loader::A
    assert loader::A::B
    assert loader::A::B::C
    assert_equal(X::B::C, loader::A::B::C)
    FAIL

    View Slide

  99. RUBY
    RUBY
    3.2
    3.2

    View Slide

  100. View Slide

  101. Foo::BAR = 42
    Foo.const_added(:BAR)
    callback

    View Slide

  102. MyFoo = mod::Foo
    Object.const_added(:MyFoo)
    callback

    View Slide

  103. module Im::ModuleConstAdded
    def const_added(const_name)
    # ...
    ::Im::ExplicitNamespace.__update_cpaths(
    prefix, "#{cpath}::#{const_name}"
    )
    super
    end
    end
    ::Module.prepend(Im::ModuleConstAdded)

    View Slide

  104. module Im::ModuleConstAdded
    def const_added(const_name)
    # ...
    ::Im::ExplicitNamespace.__update_cpaths(
    prefix, "#{cpath}::#{const_name}"
    )
    super
    end
    end
    ::Module.prepend(Im::ModuleConstAdded)

    View Slide

  105. {
    "<#Im::Loader ...>" ⇨ ...
    "<#Im::Loader ...>::Foo" ⇨ ...
    "<#Im::Loader ...>::Foo::Bar" ⇨ ...
    ...
    }
    cpath

    View Slide

  106. {
    "<#Im::Loader ...>" ⇨ ...
    "MyFoo" ⇨ ...
    "MyFoo::Bar" ⇨ ...
    ...
    }
    cpath
    Object.const_added(:MyFoo)

    View Slide

  107. assert loader::A
    X = loader::A
    assert loader::A
    assert loader::A::B
    assert loader::A::B::C
    assert_equal(X::B::C, loader::A::B::C)
    PASS

    View Slide

  108. gem "im", "~> 0.2.2"

    View Slide

  109. View Slide

  110. View Slide

  111. View Slide

  112. View Slide

  113. Chris Salzberg
    @shioyama github:shioyama/im
    github:shioyama/rails_on_im

    View Slide