Skip to content

Instantly share code, notes, and snippets.

@tagomoris
Created September 26, 2023 11:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tagomoris/811f403751f745b5360af4ecc5f74b67 to your computer and use it in GitHub Desktop.
Save tagomoris/811f403751f745b5360af4ecc5f74b67 to your computer and use it in GitHub Desktop.
Namespace on read

"Namespace on read"とは

これは、Rubyに仮想的なトップレベル名前空間を導入するためのNamespaceを提案します。NamespaceはRubyスクリプトかネイティブ拡張ライブラリかを問わず、ライブラリをグローバル名前空間から独立した形でrequire/loadします。

動機

"Namespace on read"は次の2つの問題を解決し、また1つの問題について解決の道筋を提供します。

  • ライブラリ(等)間での名前の衝突を避ける
  • グローバルに共有されたライブラリ/オブジェクトの意図しない変更を避ける
  • 依存ライブラリ間でのバージョン衝突を避ける(RubyGems, Bundlerの変更が必要)

Namespaceを使用したコード例(提案)

# your_module.rb
module YourModule
end

# my_module.rb
require 'your_module'

module MyModule
end

# example.rb
namespace1 = NameSpace.new
namespace1.require('my_module') #=> true

namespace1::MyModule #=> #<Module:0x00000001027ea650>::MyModule (or #<NameSpace:0x00...>::MyModule ?)
namespace1::YourModule # similar to the above

MyModule # NameError
YourModule # NameError

namespace2 = NameSpace.new      # Any number of namespaces can be defined
namespace2.require('my_module') # Different library "instance" from namespace1

require 'my_module' # require in the global namespace

MyModule.object_id != namespace1::MyModule.object_id #=> true
namespace1::MyModule.object_id != namespace2::MyModule.object_id

Namespace内でrequire/loadされたライブラリはモジュールやクラスのインスタンスをNamespace内に定義します。

"on read"とは

"On read"はこの提案のキーとなるアイデアで、以下の点が重要です:

  • 既存のライブラリには変更を要求しない(いくつかの限定されたケースを除く、後述)
  • Namespaceを必要としないアプリケーションには全く変更を要求しない
  • Rubyユーザーはアプリケーション全体のうち、必要な部分でのみNamespaceを有効にして使用する
  • RubyユーザーはNamespaceを必要な部分で段階的に使いはじめられます

(この名前はデータ処理関連の"Schema on read"から取りました)

予期される問題

メモリ消費の増加

Namespace内でライブラリを読み込むとグローバルな名前空間のものとは別のライブラリとして読み込まれるため、メモリ消費量が増加する。(問題にならない範囲だと考えています)

拡張ライブラリ読み込み方法の変更

Namespaceを用いて拡張ライブラリを衝突なく読み込む場合、拡張ライブラリをdlopenする際にRTLD_GLOBALを指定しているのをRTLD_LOCALに変更する必要がある。このとき、読み込まれる拡張ライブラリ本体には問題は起きない。しかし、他の拡張ライブラリのC関数を呼び出す拡張ライブラリは、シンボルが見付からなくなって動作しなくなる。

これはRuby側から拡張ライブラリに対して他拡張ライブラリのシンボルを解決する機能を提供することで解決できる。該当する拡張ライブラリ側で対応が必要(Jeremyのコメントによると、適切なマクロの導入により前バージョンと互換性を保った形で対応できそう、とのこと)。

トップレベル参照

Namespace内での::Nameをグローバル空間のトップレベルとするべきか、Namespace内トップレベルとするべきか。

  • ::ENVは環境変数を指定してほしい
  • ::Stringを指定できないとモンキーパッチするコードが書けない

またすでに存在するモンキーパッチのコード class String; def ....; end をそのまま動くようにするべきかどうかを考える必要がある。これはNamespace内では class ::String; def ...; end とさせるべきか?

@tagomoris
Copy link
Author

Namespaceを再帰的に作れる、として、その場合にトップレベル参照以外に「ひとつ外側のネームスペースを参照する」というニーズがあるか?

@tagomoris
Copy link
Author

tagomoris commented Oct 12, 2023

# multi_ver_prism.rb
module Prism
  PrismV3_2 = Namespace.new
  PrismV3_2.require('prism', version: '3.2')
  PrismV3_1 = Namespace.new
  PrismV3_1.require('prism', version: '3.1')

  def self.parse_32
    PrismV3_2::Prism.parse(...)
  end
end

# app.rb
ns1 = Namespace.new
ns1.require('multi_ver_prism')

@mame
Copy link

mame commented Oct 12, 2023

PrismV3_2 = Namespace.new.eval <<END
  gem "prism", "= 3.2"
  require "prism"
END

@tagomoris
Copy link
Author

Namespace.new.with_load_path('prism-3.2.0') { require "prism" }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment