import.rb
という Gem をリリースしました。
この Gem について解説したいと思います。
import.rb の目的
Kernel.require
を置き換えること。
Kernel.require
とは
外部ファイルを読み込むためのメソッドです。
module function Kernel.#require (Ruby 2.2.0)
また、相対パスを指定する場合は、Kernel.require_relative
を使います。
パスの指定が相対パスになる以外は、require
と同じです。
module function Kernel.#require_relative (Ruby 2.2.0)
require
の使い方例
cat.rb
と main.rb
の以下の二つのファイルが同じディレクトリに存在するとします。
- cat.rb
class Cat def meow puts 'meow meow' end end
- main.rb
# まだ require していないため Cat クラスが存在しない # Cat.new.meow # => uninitialized constant Cat (NameError) # require 先のスクリプトでCatクラスが定義される require_relative 'cat' Cat.new.meow # => meow meow
ここで、ruby main.rb
と実行すると、meow meow
と出力されます。
Kernel.require
の問題点
一番の問題点は、トップレベルにモジュールを定義せざるを得ないことです。
require
される側のファイルの目的は、何らかのモノをrequire
する側のファイルに渡すことです。
例えば、先ほどのcat.rb
は、main.rb
に対してCat
クラスを渡すことが目的だと言えます。
ここで、cat.rb
はmain.rb
にCat
クラスを渡すために、トップレベルにCat
クラスを定義しました。
これは、グローバル変数を定義することと同じです。何故ならば、トップレベルに定義されたクラスは、どこからでも参照することができるからです。
グローバル変数を使用すると、様々な問題が発生することは説明するまでもないと思います。
どこからでもアクセスできてしまう、名前の衝突、それを回避するための名前空間のネストの増大、etc...
原因
では、何故トップレベルにクラスを定義しなければいけないのでしょうか。
それは、requireが値を返さないからです(正確には、新規にロードしたかどうかがBooleanで返ってきますが、任意の値を返すことは出来ません)。
require
の戻り値を使用できない以上、トップレベルにクラスを定義しなければrequire
元にクラスを渡すことが出来ません。
提案
そこで、import.rb
という一つの提案を作成しました。
commonjsみたいなやつ
Installation
gem install import.rb
Usage
残念ながら、import.rb
自体はrequire
する必要があります。
先ほどのmain.rb
を、import.rb
を使用して書き換えてみましょう。
cat.rb
の方は書き換える必要がありません。
require 'import' # まだ import していないため Cat クラスが存在しない # Cat.new.meow # => uninitialized constant Cat (NameError) # cat.rb で定義されたCatクラスが、catというローカル変数に格納される cat = import('./cat')::Cat cat.new.meow # => meow meow # トップレベルにはCatクラスが定義されない! # Cat.new.meow # => uninitialized constant Cat (NameError)
このように、トップレベルにクラスを定義することなく、外部ファイルにて定義されているクラスを使用することが出来ました。
内部の仕組み
無名モジュールを作成し、requireする先のファイルの中身をmodule_eval
しています。
60行しかないので、コードを読んだほうがわかりやすいと思います。
import.rb/import.rb at cba99a42536360e0282d0dfbb85ea31aa09ec345 · pocke/import.rb · GitHub
FAQ
既存のライブラリ/Gemとか使えるの?
import
されるファイルがrequire
を使っていた場合、正常に動作しなかったり結局グローバルにもクラスが定義されてしまうと思います(未検証)。
import
されるファイルが先ほどのcat.rb
ぐらい単純であれば、問題なく動くでしょう。
import/require 両対応のライブラリ/Gemって作れる?
先ほどのcat.rb
ぐらい単純であれば、何もしなくても両対応になるでしょう。
複雑なものに関しては、「できたらいいなー」程度にしかまだ考えていません。
.so
とかも読み込める?
いいえ、読み込めません。.rb
ファイルのみを探索します。
ファイルを読み込むのにeval
を使用しているためです。
import_relative
ないの?
そのうち作ろうと思っています。
ただ、上記のmain.rb
のようにimport(./cat)
と呼び出してやれば相対パスを見に行くので、現状でも相対パスでのimportは可能です。
Windows でも動く?
私が知りたい(パスのセパレータが/
前提なコードを思いっきり書いているけど、Rubyがそのへんよろしくやってくれたような気がするようなしないような気がする)
プロダクションで使おうと思うんだけど、どう?
やめとけ
こうしたらいいんじゃない?
Pull Request/Issueお待ちしています!!!!!
まとめ
思いつきで初Gem作ってみた。