Slide 1

Slide 1 text

Multiverse Ruby Multiverse Ruby Chris Salzberg Chris Salzberg

Slide 2

Slide 2 text

@shioyama

Slide 3

Slide 3 text

Chris Salzberg Staff developer, Ruby and Rails Infra

Slide 4

Slide 4 text

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Sharing code in Ruby

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

In Ruby, we share code by sharing architecture.

Slide 13

Slide 13 text

that architecture is...

Slide 14

Slide 14 text

global

Slide 15

Slide 15 text

finite

Slide 16

Slide 16 text

permanent

Slide 17

Slide 17 text

Ruby’s architecture is not portable

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

Modules as Universes

Slide 20

Slide 20 text

Named Anonymous Modules

Slide 21

Slide 21 text

module Foo class Bar end end Named

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

mod = Module.new

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

// 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; }

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Modules Named Anonymous

Slide 33

Slide 33 text

Modules temporary

Slide 34

Slide 34 text

temporary == portable

Slide 35

Slide 35 text

module Foo class Bar end end

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Object::Foo Object::Baz Object::Foo::Bar Object::Foo::Qux

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

mod

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

a module is the root of a universe

Slide 45

Slide 45 text

Isolated Module Autoloader

Slide 46

Slide 46 text

require "my_gem"

Slide 47

Slide 47 text

📦= import "my_gem"

Slide 48

Slide 48 text

RUBY 3.2 RUBY 3.2

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

import load ⇨ ?

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

autoload

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

Im is a Zeitwerk fork

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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::

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

mod = import "my_gem"

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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"

Slide 73

Slide 73 text

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"

Slide 74

Slide 74 text

Im is compatible with Zeitwerk

Slide 75

Slide 75 text

実装

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

loader::Foo loader::Baz loader::Foo::Bar loader::Foo::Qux loader

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

# 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!

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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 🙈

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

The cpath problem

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

MyFoo = mod::Foo

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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 ❌

Slide 99

Slide 99 text

RUBY RUBY 3.2 3.2

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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 ✅

Slide 108

Slide 108 text

gem "im", "~> 0.2.2"

Slide 109

Slide 109 text

No content

Slide 110

Slide 110 text

No content

Slide 111

Slide 111 text

No content

Slide 112

Slide 112 text

No content

Slide 113

Slide 113 text

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