はじめに
ひとこと
アクセスしていただきありがとうございます。
下記の記事を読んで、PythonのIceCreamというライブラリやicという非常に強力な関数の存在を初めて知り、私が最も使うことの多いJavaScriptでも再現できないか試してみました。
(デバッグに強いライブラリがあることは知っていますが、スキルを上げるために敢えて再現してみます)
目的
- Pythonのic関数の基本的な機能を、我流で可能な限り再現する
検証済みの環境
- ECMAScript 2024
ご注意
- この記事の情報は 2024/11/20現在 のものです
- 我流で可能な限り再現しただけですので、 軽い気持ちで閲覧して いただけると助かります
- Pythonはオセロくらいの軽い一連のコードを書ける程度です
1.ic関数とは
概要
ic関数でどのようなことができるかは、ぜひ、上記の記事を読んでください
仕組み
Pythonには inspect というモジュールがあります
inspect は、活動中のオブジェクト (モジュール、クラス、メソッド、関数、トレースバック、フレームオブジェクト、コードオブジェクトなど) から情報を取得する関数を定義しており、クラスの内容を調べたり、メソッドのソースコードを取得したり、関数の引数リストを取り出して整形したり、詳細なトレースバックを表示するのに必要な情報を取得したりするために利用できます。
このモジュールの機能は4種類に分類することができます。型チェック、ソースコードの情報取得、クラスや関数からの情報取得、インタープリタのスタック情報の調査です。
(inspect --- 活動中のオブジェクトを調査する — Python 3.13.0 ドキュメント より引用)
IceCreamでは、これを利用してic関数を実現しているみたいです。
2.JavaScriptでic関数を再現する
目標
from icecream import ic
def add(a, b):
return a + b
result = ic(add(2, 3))
print(result)
ic| add(2, 3): 5
5
Outputの1行目のように、 実行したい関数の名前、その関数の引数の内容、その関数の戻り値を出力 することと、Outputの2行目のように、 ic関数が実行したい関数の戻り値を返すこと を目標にします。
仕組み
まずJavaScriptにはPythonのinspectにあたるものはありません。
また下記のようにPythonの書き方をそのまま移植しても、ic関数の引数fncにはadd関数の戻り値が渡されるため、上手くいきません。
function add(a, b) {
//省略
}
function ic(fnc) {
//省略
}
ic(add(2, 3))
そのためic関数の引数に実行したい関数を実行しない形で渡す方法か、別の方法をとる必要があります。
アイデア1_実行したい関数とその引数のそれぞれをic関数の引数に渡す
実行したい関数とその引数のそれぞれをic関数の引数に渡す方法です。
function add(a, b) {
return a + b
}
function ic(fnc, ...args) {
const value = fnc(...args)
console.log(`ic| ${fnc.name}(${[...args].join(', ')}): ${value}`)
return value
}
const result = ic(add, 2, 3)
console.log(result)
ic| add(2, 3): 5
5
add(2, 3)
ic(add, 2, 3)
シンプルで安全性も高いものの、書き換えに少し手間がかかります
アイデア2_eval関数を使用する
実行したい文を文字列としてic関数の引数に渡して、ic関数内でeval関数を使用する方法です。
function add(a, b) {
return a + b
}
function ic(fnc) {
const value = eval(fnc)
console.log(`ic| ${fnc}): ${value}`)
return value
}
const result = ic('add(2, 3)')
console.log(result)
ic| add(2, 3): 5
5
add(2, 3)
ic('add(2, 3)')
ic関数の引数がすっきりしますが、eval関数を使うことが気になります
アイデア3_Function.prototypeを拡張する
Function.prototypeを拡張しic関数内で実行したい関数を実行する方法を考えました。
Function.prototype.ic = function() {
const value = this(...arguments)
console.log(`ic| ${this.name}(${[...arguments].join(', ')}): ${value}`)
return value
}
function add(a, b) {
return a + b
}
const result = add.ic(2, 3)
console.log(result)
ic| add(2, 3): 5
5
add(2, 3)
add.ic(2, 3)
とても簡単に書き換えられますが、グローバルな名前空間の汚染が気になります。
アイデア4_Functionコンストラクターを使用する
2024/11/20 コメントで教えていただいた内容を基に加筆しました
@mashuel 様、ありがとうございます!
実行したい文を文字列としてic関数の引数に渡して、ic関数内でFunctionコンストラクターを使用する方法です。
function add(a, b) {
return a + b
}
function ic(fnc) {
const value = Function(`return ${fnc}`)()
console.log(`ic| ${fnc}): ${value}`)
return value
}
const result = ic('add(2, 3)')
console.log(result)
ic| add(2, 3): 5
5
add(2, 3)
ic('add(2, 3)')
ic関数の引数がすっきりしますが、(このコードにおいて、グローバルスコープで実行されることが問題になるような場合が思いつきませんが)グローバルスコープで実行される関数のみを生成することが問題にならないか気になります。
比較
- アイデア1_実行したい関数とその引数のそれぞれをic関数の引数に渡す
- アイデア2_eval関数を使用する
- アイデア3_Function.prototypeを拡張する
- アイデア4_Functionコンストラクターを使用する
| アイデア1 | アイデア2 | アイデア3 | アイデア4 | |
|---|---|---|---|---|
| 書き換えの手間 | △ | △ | 〇 | △ |
| 安全性 | 〇 | × | △ | △ |
PythonのinspectにあたるものがJavaScriptにないため、いずれのアイデアも一長一短にはなってしまいます。
個人的には、ic関数はデバッグのために使い本番環境では使わないことを考えると、容易に書き換えられるアイデア3がいいと思います。
おわりに
最後までお読みいただきありがとうございます。
JavaScriptでic関数を再現できないかなぁと思い1時間ほど考えてみただけですので、改良できる部分や、別のアイデアがあるかもしれません。思いついたら加筆していきます。
また私ならばこうするというアイデアがありましたらコメントしていただけますと、私も勉強になりますので、ぜひよろしくお願いします。