パーサコンビネータライブラリを更新した (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 の型定義を用意したのはこの件へのレスポンスです. なんというか, なんかいろいろですね.

Node.js のパッケージ情報をいい感じに表示するやつを作った

ppp という, Node.js のパッケージ情報を表示するコマンドラインツールを作ったのでそのご紹介です.

github.com

それ npm view で良いじゃん, とか, 前も似たようなの作っていなかった? などと思った方もおられると思いますが, 新しく作ったのにはちゃんと理由がありますので, まずはお読みください...

背景

世の中には OS / プログラミング言語ごとに様々なパッケージマネージャがありますが, これらには大体 info サブコマンドのような機能があって, パッケージの情報を表示することができます. 例としていくつか挙げるとこんな感じです:

  • Homebrew
$ brew info node
node: stable 11.7.0 (bottled), HEAD
Platform built on V8 to build network applications
https://nodejs.org/
Not installed
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/node.rb
==> Dependencies
Build: pkg-config , python@2 
Required: icu4c 
==> Options
--HEAD
    Install HEAD version
==> Analytics
install: 289,885 (30 days), 959,391 (90 days), 3,944,852 (365 days)
install_on_request: 220,363 (30 days), 722,068 (90 days), 2,846,827 (365 days)
build_error: 0 (30 days)
  • Cabal
$ cabal info array
* array            (library)
    Synopsis:      Mutable and immutable arrays
    Versions available: 0.3.0.2, 0.3.0.3, 0.4.0.0, 0.4.0.1, 0.5.0.0, 0.5.1.0,
                        0.5.1.1, 0.5.2.0, 0.5.3.0 (and 4 others)
    Versions installed: 0.5.2.0
    Homepage:      [ Not specified ]
    Bug reports:   http://ghc.haskell.org/trac/ghc/newticket?component=libraries%20%28other%29&keywords=array
    Description:   In addition to providing the "Data.Array" module
                   <http://www.haskell.org/onlinereport/haskell2010/haskellch14.html
                   as specified in the Haskell 2010 Language Report>, this
                   package also defines the classes 'IArray' of immutable arrays
                   and 'MArray' of arrays mutable within appropriate monads, as
                   well as some instances of these classes.
    Category:      Data Structures
    License:       BSD3
    Maintainer:    libraries@haskell.org
    Source repo:   http://git.haskell.org/packages/array.git
    Dependencies:  base >=4.9 && <4.13
    Documentation: /usr/local/Cellar/ghc/8.4.4/share/doc/ghc-8.4.4/html/libraries/array-0.5.2.0
    Cached:        No
    Modules:
        Data.Array
        Data.Array.Base
        Data.Array.IArray
        Data.Array.IO
        Data.Array.IO.Internals
        Data.Array.IO.Safe
        Data.Array.MArray
        Data.Array.MArray.Safe
        Data.Array.ST
        Data.Array.ST.Safe
        Data.Array.Storable
        Data.Array.Storable.Internals
        Data.Array.Storable.Safe
        Data.Array.Unboxed
        Data.Array.Unsafe
  • opam
$ opam info base

<><> base: information on all versions ><><><><><><><><><><><><><><><><><><>  🐫 
name                   base
all-installed-versions v0.11.0 [4.06.1]
all-versions           v0.9.0  v0.9.1  v0.9.2  v0.9.3  v0.9.4  v0.10.0  v0.11.0  v0.11.1

<><> Version-specific details <><><><><><><><><><><><><><><><><><><><><><><>  🐫 
version       v0.11.1
repository    default
url.src:      "https://github.com/janestreet/base/releases/download/v0.11.1/base-v0.11.1.tbz"
url.checksum: "md5=e7e7dc5db3f1fea19d74a31bbd4ac621"
homepage:     "https://github.com/janestreet/base"
bug-reports:  "https://github.com/janestreet/base/issues"
dev-repo:     "git+https://github.com/janestreet/base.git"
authors:      "Jane Street Group, LLC <opensource@janestreet.com>"
maintainer:   "opensource@janestreet.com"
license:      "Apache-2.0"
depends:      "ocaml" {>= "4.04.1"}
              "sexplib0" {>= "v0.11" & < "v0.12"}
              "jbuilder" {build & >= "1.0+beta18.1"}
depopts:      "base-native-int63"
synopsis      Full standard library replacement for OCaml
description   Full standard library replacement for OCaml
              Base is a complete and portable alternative to the OCaml standard
              library. It provides all standard functionalities one would expect
              from a language standard library. It uses consistent conventions
              across all of its module.
              Base aims to be usable in any context. As a result system dependent
              features such as I/O are not offered by Base. They are instead
              provided by companion libraries such as stdio:
                https://github.com/janestreet/stdio

こういったコマンドがあるといちいちパッケージリポジトリを見に行ったりしなくて良いので大変便利ですね.

我らが npm には npm view (エイリアスとして npm infonpm show も使える) というサブコマンドがあります. ところがこれは一昔前はこんな出力をしていました1:

$ npm --version
5.8.0
$ npm view react-dom
 
{ name: 'react-dom',
  description: 'React package for working with the DOM.',
  'dist-tags': 
   { latest: '16.7.0',
     next: '16.8.0-alpha.1',
     canary: '0.0.0-3e15b1c69',
     tmp: '16.3.3',
     unstable: '0.0.0-0c756fb-f7f79fd' },
  versions: 
   [ '0.0.0-0c756fb-697f004',
     '0.0.0-0c756fb-f7f79fd',
     '0.0.0-3e15b1c69',
     '0.0.0-4a1072194',
     '0.0.0-7325ebe4d',
... (以下 500 行ほど続く)

JSONconsole.log しただけ, みたいな雑な感じなのはこの際許すとして, 平気で数百行単位で出力されるため, とても人間が読むことはできませんでした. (といっても頑張って読んだりしていた... またはいちいち npm view react-dom version みたいにして個々のフィールドを見ていた.)

これではあまりに不便に感じたため, 以前この npm view をラップして, いい感じに結果を整形して表示してくれるコマンド npm-sum を作りました.

susisu.hatenablog.com

これを使えばそこそこ見やすい形で出力してくれます:

$ npm-sum react-dom
  Name         : react-dom
  Version      : 16.7.0 (12/20/2018, 10:20:03 AM)
  Homepage     : https://reactjs.org/
  License      : MIT
  Description  : React package for working with the DOM.
  Keywords     : react
  Tags         :
    canary   : 0.0.0-3e15b1c69 (1/15/2019, 7:34:29 AM)
    latest   : 16.7.0 (12/20/2018, 10:20:03 AM)
    next     : 16.8.0-alpha.1 (1/16/2019, 8:22:20 AM)
    tmp      : 16.3.3 (8/2/2018, 3:58:58 AM)
    unstable : 0.0.0-0c756fb-f7f79fd (11/13/2018, 12:47:42 PM)
  Dependencies :
    loose-envify@^1.1.0, object-assign@^4.1.1, prop-types@^15.6.2,
    scheduler@^0.12.0

一方, 幸い現代では npm view も改良され2, 人間に読める形で表示されるようになりました.

$ npm --version
6.7.0
$ npm view react-dom

react-dom@16.7.0 | MIT | deps: 4 | versions: 135
React package for working with the DOM.
https://reactjs.org/

keywords: react

dist
.tarball: https://registry.npmjs.org/react-dom/-/react-dom-16.7.0.tgz
.shasum: a17b2a7ca89ee7390bc1ed5eb81783c7461748b8
.integrity: sha512-D0Ufv1ExCAmF38P2Uh1lwpminZFRXEINJe53zRAbm4KPwSyd6DY/uDoS0Blj9jvPpn1+wivKpZYc8aAAN/nAkg==
.unpackedSize: 4.5 MB

dependencies:
loose-envify: ^1.1.0  object-assign: ^4.1.1 prop-types: ^15.6.2   scheduler: ^0.12.0    

maintainers:
- acdlite <npm@andrewclark.io>
- brianvaughn <briandavidvaughn@gmail.com>
- fb <opensource+npm@fb.com>
- flarnie <flarnie.npm@gmail.com>
- gaearon <dan.abramov@gmail.com>
- sebmarkbage <sebastian@calyptus.eu>
- sophiebits <npm@sophiebits.com>
- threepointone <threepointone@gmail.com>
- trueadm <dg@domgan.com>
- zpao <paul@oshannessy.com>

dist-tags:
canary: 0.0.0-3e15b1c69          next: 16.8.0-alpha.1             unstable: 0.0.0-0c756fb-f7f79fd  
latest: 16.7.0                   tmp: 16.3.3                      

published a month ago by acdlite <npm@andrewclark.io>

では自作の npm-sum を捨てて npm view を使うようになったかと言うと, そうはなりませんでした. というのも以下のような点で不満があったためです.

  • 情報について:
    • dist.tarball や .shasum を見て使うのはパッケージマネージャであって, 人間ではないのでは?
    • dependencies を見て何か思うことはあまりなくて, むしろ peerDependencies の方が requirements なので知りたい
    • maintainers を並べられても特に嬉しいとは思わない
  • 色について:
    • deps: 4versions: 135 で数字の色が違うけれど, versions の方は黄色だから警告とかの意味があるのかな? と思うけど実は特に意味はない
    • 全体的にただカラフルなだけで, あまり整理されているような印象は受けず, 読みやすいとは思わない
  • フォーマットについて:
    • 見出し: 内容 のような構成なのかと思ったら例外も多かったり, 急に自然言語になったりして, 統一感がないように感じる
    • 最後に書いてある何時 publish されたかは割と知りたいし, 最初に書いてあるバージョンに関連した情報であって近くにある dist-tags の各バージョンとは関連しないので, どちらかといえば最初の方に書いてあって欲しい

こういった理由で npm-sum を使い続けていたわけですが, こちらも完璧な出来というわけではありません. 再度出力例を挙げますが,

$ npm-sum react-dom
  Name         : react-dom
  Version      : 16.7.0 (12/20/2018, 10:20:03 AM)
  Homepage     : https://reactjs.org/
  License      : MIT
  Description  : React package for working with the DOM.
  Keywords     : react
  Tags         :
    canary   : 0.0.0-3e15b1c69 (1/15/2019, 7:34:29 AM)
    latest   : 16.7.0 (12/20/2018, 10:20:03 AM)
    next     : 16.8.0-alpha.1 (1/16/2019, 8:22:20 AM)
    tmp      : 16.3.3 (8/2/2018, 3:58:58 AM)
    unstable : 0.0.0-0c756fb-f7f79fd (11/13/2018, 12:47:42 PM)
  Dependencies :
    loose-envify@^1.1.0, object-assign@^4.1.1, prop-types@^15.6.2,
    scheduler@^0.12.0
  • 情報の取捨選択ができていない:
    • とりあえず全ては生の JSON よりマシという気持ちで雑に作ったため, あまり取捨選択はしていなかった
    • 例えば npm view のところでも書いたように, dependencies よりも peerDependencies が表示されて欲しい
  • 知りたい情報の変化:
    • 例えば最近だと TypeScript 向けの型情報が提供されるかもわかると嬉しいと思う
  • 表示のカスタマイズ性:
    • ここまで この情報は知りたい とか 知りたくない とか書いているけれど, これは人によって違う可能性がある

といった課題が見つかってきます.

元々 npm-sum は「パッケージの情報を人間にも読めるように表示する」というコンセプトで作ったわけですが, npm view が人間にも読めるようになってしまった以上, この点については最早 npm-sum の強みではなくなってしまいました. ということで, ここまでに npm viewnpm-sum のそれぞれで挙げたような課題を解決し, 「パッケージの情報を適切に取捨選択した上で, 見やすい形で表示する」というコンセプトで新たなツールを作ることにしました.

ppp

完成したものがこちらです.

github.com

以下のコマンドでインストールできます.

npm i -g @susisu/ppp

使い方は以下のように npm view で取得した JSON を標準出力から受け取ります. これは npm-sum のように npm view をラップして動かす設計は責任が増えて筋が悪いなと思ったのと, 昨今のツールの多様化を考慮してです. 名前に npm を含まないのもそのため.

npm view react-dom --json | ppp

とはいえ面倒なので, 私は以下のようなスクリプトns という名前で作って ns react-dom みたいに使っています (標準で同梱しても良いかもしれない). ns が何の略なのかはご想像におまかせします.

#!/bin/sh

npm view "$@" --json | ppp

2019/02/03 追記: やっぱり ppp react-dom みたいに直接パッケージ名を指定しても動くようにしました. 利便性には勝てなかった.

閑話休題, デフォルトの設定では以下のような表示になります:

$ npm view react-dom --json | ppp
  Name              : react-dom
  Version           : 16.7.0 (12/20/2018, 10:20:03 AM)
  Description       : React package for working with the DOM.
  License           : MIT
  Homepage          : https://reactjs.org/
  Peer Dependencies : react@^16.0.0
  Tags              :
    canary   : 0.0.0-3e15b1c69 (1/15/2019, 7:34:29 AM)
    latest   : 16.7.0 (12/20/2018, 10:20:03 AM)
    next     : 16.8.0-alpha.1 (1/16/2019, 8:22:20 AM)
    tmp      : 16.3.3 (8/2/2018, 3:58:58 AM)
    unstable : 0.0.0-0c756fb-f7f79fd (11/13/2018, 12:47:42 PM)

デフォルト設定は見て分かる通りかなり表示項目を絞っていますが, 設定ファイルを使うことで表示項目をカスタマイズできるようになっています. 詳しくは README を参照してください.

私の場合は以下のように設定して, ES Modules や TypeScript 用の型定義の有無なども表示するようにしています.

fields:
  - name
  - version
  - description
  - license
  - author
  - homepage
  - npm
  - repository
  - module
  - types
  - binaries
  - engines
  - peerDependencies
  - tags
wrap: 120

こうしておくと, 例えば react-dom を TypeScript で使うときは別途型定義をインストール必要があるとか, 一方で rxjs の場合は必要ないといったことがわかって便利です.

$ npm view react-dom --json | ppp
  Name              : react-dom
  Version           : 16.7.0 (12/20/2018, 10:20:03 AM)
  Description       : React package for working with the DOM.
  License           : MIT
  Homepage          : https://reactjs.org/
  NPM               : https://www.npmjs.com/package/react-dom
  Repository        : git+https://github.com/facebook/react.git
  Module            : No
  Types             : No
  Peer Dependencies : react@^16.0.0
  Tags              :
    canary   : 0.0.0-3e15b1c69 (1/15/2019, 7:34:29 AM)
    latest   : 16.7.0 (12/20/2018, 10:20:03 AM)
    next     : 16.8.0-alpha.1 (1/16/2019, 8:22:20 AM)
    tmp      : 16.3.3 (8/2/2018, 3:58:58 AM)
    unstable : 0.0.0-0c756fb-f7f79fd (11/13/2018, 12:47:42 PM)
$ npm view rxjs --json | ppp
  Name        : rxjs
  Version     : 6.3.3 (9/26/2018, 8:52:58 AM)
  Description : Reactive Extensions for modern JavaScript
  License     : Apache-2.0
  Author      : Ben Lesh <ben@benlesh.com>
  Homepage    : https://github.com/ReactiveX/RxJS
  NPM         : https://www.npmjs.com/package/rxjs
  Repository  : git+https://github.com/reactivex/rxjs.git
  Module      : Yes
  Types       : Yes
  Engines     : npm@>=2.0.0
  Tags        :
    alpha          : 6.0.0-alpha.4 (3/14/2018, 4:00:55 AM)
    beta           : 6.0.0-beta.4 (3/30/2018, 5:15:32 AM)
    forward-compat : 5.6.0-forward-compat.5 (5/22/2018, 2:08:43 PM)
    latest         : 6.3.3 (9/26/2018, 8:52:58 AM)
    rc             : 6.0.0-uncanny-rc.7 (4/14/2018, 6:55:37 AM)
    smoosh         : 6.0.0-smoosh.1 (4/2/2018, 5:21:39 AM)
    unsmoosh       : 6.0.0-smoosh.2 (4/2/2018, 12:23:50 PM)

よかったですね〜.

おまけ

ところで Yarn にも yarn info というコマンドがありますが, こちらはどうでしょうか?

$ yarn info react-dom
yarn info v1.13.0
{ name:
   'react-dom',
  description:
   'React package for working with the DOM.',
  'dist-tags':
   { latest:
      '16.7.0',
     next:
      '16.8.0-alpha.1',
     canary:
      '0.0.0-3e15b1c69',
     tmp:
      '16.3.3',
     unstable:
... (以下 900 行ほど続く)

......


  1. 一応公平性のために言っておくと, 例として react-dom の情報を見ているのは後の話の都合上で, 私自身特に思い入れはないです.

  2. この当たり前のような機能を搭載した npm 5.10.0 がリリースされたのはなんと 2018 年 5 月 12 日 (より正確には 5.9.0-next.0 で 2018 年 3 月 24 日) で, なんとこの記事を書いている一年前の時点ではまだなかった