Object.create(null)

TypeError: Cannot convert object to primitive value

パーサコンビネータライブラリを更新した (2 年ぶり 2 回目)

2 年に一回更新することでおなじみの (?) JS 製パーサコンビネータライブラリ loquat の v3 をリリースしました.

github.com

変更点の詳細はここには書かないのでリポジトリを見てください. どうせ誰も使っていないでしょうし...

以下は雑な話題です.

TypeScript

現代において TypeScript 向けの型情報が提供されないならば即ち使いにくい, と言っても過言ではないくらいでしょう. ということで型情報を含んで単純化したインターフェースを提供することにしました.

www.npmjs.com

今回の更新の主な内容はこれで, ある程度 TypeScript フレンドリーになるようにインターフェースをいくらか変更しています.

TypeScript で書き直さないのですか

いいえ. 正直 TypeScript で書き直すメリットが無いです. ただし決して動的型付けこそ至高みたいな意味ではないです.

TypeScript の型システムはある面 (例えば古くからある JS の混沌としたインターフェースに無理やり型をつける) ではとても強力なものの, 一方で Haskell, Scala, OCaml などと比較すると型システム自体や型推論においてかなり非力な面が目立ちます. loquat の元ネタは Parsec なので, 機能を完全に保ったまま真面目に型をつけようとすると Haskell か, おそらく ScalaOCaml 相当の機能 (associated type や dependent object 的なもの, あるいは functor, さらに多相な値とか) が必要になってきます. またそれが実現したところで型推論が貧弱では, 開発している私も, さらにはユーザーも冗長な型定義を書く羽目になり大変不便です.

完全に型をつけるのを諦めた上で TypeScript で書くこともできますが, 単に any だらけになって読みづらい上に特に安全性も増していない, みたいな状態になりそうだったのでやりませんでした. また機能を削ることで綺麗に型をつけることもできますが, これも技術的面白みが無くなるのでやるつもりはないです.

上で書いた TypeScript 用の型定義は, まさに本来提供している機能から難しい部分を削っています. それでもきっと 99% のユーザーにとっては十分なんじゃないでしょうか. 知らんけど.

パフォーマンス

ここで雑に計測してます.

github.com

たぶん世にある JS 製パーサコンビネータの中では最速で, 前回のバージョンから特に劣化はしていません. いい感じに書けているのか Node.js (V8) のバージョンが上がるとちょいちょい相対的に速くなります. parsimmon はバージョン上げたらエラーメッセージが豪華になったらしく勝手に遅くなっていきました.

monorepo

以前からプラグイン方式を採用しているためにパッケージが複数リポジトリに散らばっており, 開発中に yarn link などするのも大変だったので, 今流行りの Lerna ってやつで monorepo にしてみました. なんかまだあまりこなれた開発フローを構築できていないですが, 使っているうちにきっと慣れていくでしょう. というか使わなければ慣れることもないわけで.

愚痴

JavaScript でパーサコンビネータを書くのにあたって技術的課題はいくつかあって, これまでに都度解決策を考えてきました.

で, 先日これらをまったく解決していないライブラリが「TypeScript で書かれていて使いやすい」と受け入れられているのを見て, まあユーザー観点から見たら仕方がないのかなという気持ちはありつつも, かなり残念に思いました (これは相当に丸めた表現です).

今回単純化したインターフェースと TypeScript の型定義を用意したのはこの件へのレスポンスです. なんというか, なんかいろいろですね.