[JavaScript] 型扱いに関するトリビア集

この度、世界一(自称)バグの少ないソフトハウスを勝手に名乗る弊社より、バグゼロを目指す開発手法の一要素といたしまして、GitHubにて各種言語向け機能群 [Yggdrasil Essence](https://github.com/ylllc) の公開をはじめました。 
今後も少しずつではありますが、続々まとめていく予定です。
なお、Unlicenseにしてありますので、お好きなように。

さて、この度Node.jsおよびDeno版に[追加した機能](https://github.com/ylllc/yges_js_node/blob/main/api/util.js)について軽く解説しておきましょうか。  

# 早い話

適当に流用してないで、意味のある定義をしよう。
JavaScriptに限らず。

…で終わっちゃいそうなネタではありますが、まー一応。

# if(変数) への畏怖

いい感じにtrueかfalseを決めてくれるので多用される記述ですね。
だがちょっと待ってほしい。
具体的にどんな判定されているか、把握してますか?

- booleanについては、値通り
- 数値については、 0 でなければtrue
  - …って想定してると、 NaN がfalseと判定される罠に注意
- 文字列については、空文字列でなければtrue
- オブジェクトについては、nullでなければtrue
- undefinedはfalse

# === が厳密比較だと思い込んでない?

例外として、 NaN === NaN はfalseになります。

# では isNan() でどうだ

NaNだけでなく、数値に変換できない値全てがtrueになります。
それで

- isNaN(null) → false
- isNaN(undefined) → true
- isNaN(false) → false
- isNaN([false]) → true

…ヮヶヮヵ。
なお、公式で Number.isNaN() ができてまして、これならきっちり NaN だけ検出できます。

# そもそもNaNって何ぞ?

ひとことでいえば、実数で表せない、無限大以外の何か。
なので、
- 0ではありません (true?)
- 無効な値です (false?)
- 比較しようがありません (どっちでもない?)

ごっちゃごちゃに扱ってると、矛盾を抱えてますね。
これが安易に扱ってはならない理由です。

# == Infinity

何故か文字列の 'Infinity' もtrueになります。
文字種限定のようで 'infinity' だとfalseになります。
この点、 === Infinity なら厳密に Infinity との比較になります。

それにしても、 Infinity と -Infinity を毎度個別に比較するの面倒なので、Yggdrasil Essenceでは isJustInfinity() ができました。

# isFinite() って?

[解説](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/isFinite)見ても今一つ趣旨が謎ではあり。  
isFinite(null) はtrueだけど isFinite(undefined) はfalseとか。
あと数値変換できない値がfalseになる。  

Number.isFinite() なら文字列やnullもfalseになって、だいぶすっきり。
でもｺﾚｼﾞｬﾅｲんだ。Infinityの方を検出したいんだ。

# 空のバリアント値を検出したい

正確には、nullとundefinedと空文字列。
(空の配列やオブジェクトは「オブジェクトが存在する」って解釈でいいかな? めんどいしｫ)
通常の if(!変数) だと0やfalseやNaNが引っかかるのでボツ。
というわけで、Yggdrasil Essenceでは isEmpty() ができました。

# 無効なバリアント値を検出したい

正確には、nullとundefinedとNaN。
通常の if(!変数) だと0やfalseや空文字列が引っかかるのでボツ。
というわけで、Yggdrasil Essenceでは isValid() ができました。

# 汎用的な二値判定をしたい

if(変数) は、NaNがfalseになるのが気に入らない。
あと外部連携でnullやfalseが文字列になってるケースが度々ありまして、場合により対応したいこともあるので、これはオプション機能として。
というわけで、Yggdrasil Essenceでは文字列のnullやfalseやundefinedや00.00なんかもfalseに判定する boolernize() ができました。

# 汎用的な三値判定をしたい

非同期処理ガチ勢といたしましては、はbooleanに加え未定を表す表現をnullとしてよく使いますね。
というわけで、上記二値判定のうちnullやundefinedをnullと判定する trinarize() ができました。

# ゼロ埋めは padStart() で…って、ちょっと待て

負数をかますと 00-123 とかになります。
限りなくかっちょ悪いぞ。
(空白埋めならこれで正解なんだけどね)
というわけで、Yggdrasil Essenceでは符号をちゃんと左端に置いてくれる zerofill() ができました。
ついでに、オプション機能として左端に + を付けることもできます。

# ログ出し用の文字列

普通は toString() で充分…ではなくて、オブジェクトの中身も出したいのですよ。
あとnullとかundefinedとか食わせるとErrorぶん投げてくるやん。
というわけで、Yggdrasil Essenceでは justString() ができました。

# デバッグ用の文字列

デバッグ用には、文字列を区別したりオブジェクト内容を書き出すために JSON.stringify() が代用されたりしますね。
概ねこれでいいんですが、undefinedやInfinityが化けます。
というわけで、Yggdrasil Essenceでは inspect() ができました。

# そもそもTypeScriptでよくね?

TypeScript最大の問題点といたしまして、あまりに型合わせに神経質になりすぎているがためにエラー対応専用の余計なコーディングが増え、それが却ってコードの品質を落としている気配が多々見受けられます。

それに、どっちにしても念入りなテストは要るんですよ。
であれば、エラー対応に費やす時間あるならテスト工程に手間かける方が有意義といえます。
というわけで、筆者が自ら進んでTypeScriptを扱うことはありません。
(ただし今やTypeScriptの方が主流であることも確かなので、必要に応じ対応はできます)

# ついでに

同じソースファイルに前から入ってた便利機能を紹介しましょう。
JavaScriptではループ内に非同期処理を書くとよく変数が化けるところが悩みの種で、回避のために ((キープされた変数群)=>{非同期処理})(キープしたい変数群) とかいったトリッキーなコーディングが要求されます。
これがとても不健全なので、Yggdrasil Essenceでは safeStepIter() safeArrayIter() safeDictIter() ができました。
使い方は[仮ドキュメント](https://github.com/ylllc/yges_js_web/blob/main/doxy/features/safeiter.md)参照。