概要

前回、 CommonJS のエクスポートについての緑さん研究ノートを書いた。

この研究ノートでは、 module というモジュールスコープ変数が、モジュール内の値をどう外部へエクスポートするか、というところに主眼を置いていた。でもここまで来たら、インポートを行う側である require の中身もちょっと覗いてみたくなるものだ。

といって覗いてみたら面白いコトになっていて最終的に Python の話になっちゃったハナシするぞ。コイツいつも Python の話してんな。

 

require 実験ノート

とりあえず覗いてみようぜ。

node -v
# v20.3.0

node --eval 'console.info({ require })'
# {
#   require: [Function: require] {
#     resolve: [Function: resolve] { paths: [Function: paths] },
#     main: undefined,
#     extensions: [Object: null prototype] {
#       '.js': [Function (anonymous)],
#       '.json': [Function (anonymous)],
#       '.node': [Function (anonymous)]
#     },
#     cache: [Object: null prototype] {}
#   }
# }

うん? なんか正直、 require は function ですよ、と言われるくらいのものだと思っていたのだけど、色々出てきたな。どうやら require は関数であると同時にオブジェクトでもあるらしい。

 

require.main に注目してみたら結局 Python の話になった

まず注目してみたいのは require.main かな。コマンドライン上で見てみると undefined だけれど、ファイルの中で require を見ると、そのファイルの module が入っているんだよ。

echo 'console.info({ require })' > baz.js
node baz.js
# {
#   require: [Function: require] {
#     resolve: [Function: resolve] { paths: [Function: paths] },
#     main: Module {
#       id: '.',
#       path: '/Users/user/Documents/GitHub/ts-lab/js-commonjs-export-lab',
#       exports: {},
#       filename: '/Users/user/Documents/GitHub/ts-lab/js-commonjs-export-lab/baz.js',
#       loaded: false,
#       children: [],
#       paths: [Array]
#     },
#     extensions: [Object: null prototype] {
#       '.js': [Function (anonymous)],
#       '.json': [Function (anonymous)],
#       '.node': [Function (anonymous)]
#     },
#     cache: [Object: null prototype] {
#       '/Users/user/Documents/GitHub/ts-lab/js-commonjs-export-lab/baz.js': [Module]
#     }
#   }
# }

調べてみると、実は “ファイルの module が入っている” わけではなくて、 node で起動した最初のモジュールの module が入っているらしい。つまり、 Python の if __name__ == "__main__" が JavaScript でも出来るってことだよ! な、なんだってーー!!

echo 'if (require.main === module) { console.info("I am main!"); }' > qux.js

# qux.js を起動すると、この if 文が true になるが……
node qux.js
# I am main!

# qux.js を別ファイルから呼び出すと、この if 文は false になる。
node --eval 'require("./qux.js")'
# (なんも表示されない)

 

関数であると同時にオブジェクトでもあるってところに注目してみたら結局 Python の話になった

え、 JavaScript って関数と同時にオブジェクト、なんてコトができるの?

node --eval 'const foo = (x, y) => x + y;
foo.bar = 1;
console.info(foo.bar);
console.info(foo(1, 2));
console.info({ foo });'
# 1
# 3
# { foo: [Function: foo] { bar: 1 } }

できたよ。マジか。やれやれ、こんな危なっかしいコト、ぼくの親愛なる Python には出来な……

def foo(x: int, y: int) -> int:
    return x + y

foo.bar = 1
print(foo.bar) # 1
print(foo(1, 2)) # 3
print(type(foo), vars(foo)) # <class 'function'> {'bar': 1}

出来んのかい!!

まあもちろん、型ヒント的にはめちゃくちゃなコトやっているから、 mypy でチェックすると怒られるけどな。このように↓

foo.bar = 1
# "Callable[[int, int], int]" has no attribute "bar"  [attr-defined]

ちゃんと型ヒントを書くとしたら、こうしないといけないね↓

class Foo:
    def __init__(self):
        self.bar = 0

    def __call__(self, x: int, y: int) -> int:
        return x + y

foo = Foo()
foo.bar = 1

print(foo.bar)  # 1
print(foo(1, 2))  # 3
print(type(foo), vars(foo))  # <class '__main__.Foo'> {'bar': 1}

うーん、良い Python だ。 Callable であり、 bar というインスタンス変数を持つことが明確で、良いね。 JavaScript (CommonJS) の調査をしていたはずが、どうしても思考が Python にイッちまうな。