PureScript は、 Haskell ライクな altJS である。「Haskell ライク」という評判に劣らず、 Haskell の最も重要な特徴である型クラスをきちんと受け継いでいる。
現在のバージョンは 0.11.7 である。したがってまだ安定しているとは言えない。バージョン 1.0 まで解決されるべき Issues はこれだけある。
特徴
Haskell との設計上の違いは、以下のページにまとまっている:
documentation/Differences-from-Haskell.md at master · purescript/documentation
その中でも大きなものを、筆者の独断と偏見で2つ挙げる:
- 正格評価
- レコード型と row polymorphism
その他の特徴:
- Rank N polymorphism (RankNTypes) がある
- MultiParamTypeClasses, FunctionalDependencies みたいなやつがある
- コンパイル後の JavaScript がある程度「読める」ことを目標にしている:
- 型クラスのインスタンスには名前をつけなければならない(正直言ってだるいのだが…)
- 演算子は、アルファベットの名前がついた関数のエイリアスとなっている。例:
(+)
はadd
のエイリアス、(>>=)
はbind
のエイリアス - ランタイムライブラリーは最小限となるようにしている。例えば、標準では多倍長整数は含まれない。
- Prelude が標準でインポートされないし、 Prelude を使うにはパッケージを取ってくる必要がある
細かい違いは、スーパークラスを持つ型クラスを定義するときの矢印の向きが逆 (Haskell 流の =>
ではなく、論理的な関係を重視して <=
) だとか、関数型に対する複数の型クラス制約は (Hoge a, Piyo a) => ...
ではなくて Hoge a => Piyo a => ...
と書くとか、たくさんある。
まだ実装されていない機能
Ideas which Need Proposals · Issue #2749 · purescript/purescript
Type System Wishlist · Issue #1719 · purescript/purescript
筆者が使ってみて、物足りないと感じたものをいくつか挙げる:
- 型クラスのメソッドのデフォルト実装 :Default method implementations · Issue #3067 · purescript/purescript
- GADTs
- Rank 2 多相があるので、一応エミュレートできるようである:
- 存在型 (Existentials) Existential Types · Issue #82 · purescript/purescript
- ライブラリーによる実装があるし、もちろん、 Rank N Types でエミュレートできる。
- Pattern Synonyms: Pattern Synonyms · Issue #1667 · purescript/purescript
- JavaScript から見えるデータ構造(フィールドの名前とか)と PureScript から見えるデータ構築子を変えたい場合に Pattern Synonyms があると便利だと思う。
- インライン化:Inlining · Issue #2345 · purescript/purescript
- インライン化・特殊化は欲しい。
- 現状、 Number と Int に対する + などのビルトイン演算はコンパイラーが特別扱いしているようで、型がわかっていれば辞書経由の間接関数呼び出しが発生しない。
- コンパイル後の JavaScript に対して最適化をかける代物があるらしい: https://github.com/Pauan/rollup-plugin-purs
- 型レベル自然数:Add type-level natural numbers · Issue #2899 · purescript/purescript, Type-level naturals by kcsongor · Pull Request #3058 · purescript/purescript
ターゲット言語
ECMAScript 3 か 5 をターゲットにしているようだ。(JavaScript 以外のバックエンドもいくつか存在しているようだが、どのぐらいメンテされているのかは不明: documentation/Alternate-backends.md at master · purescript/documentation)
そのため、 Int 型の掛け算に対して 32ビット整数としての乗算 (Math.imul) ではなくて浮動小数点数としての乗算 a*b|0
を使っている: Use Math.imul(a, b) instead of a * b | 0 · Issue #2980 · purescript/purescript
また、 JavaScript の Math オブジェクトにあるような数学関数は purescript-math に入っているのだが、 ECMAScript 6 で追加された数学関数は含まれない: Math – purescript-math – Pursuit
どうも IE が ES6 をサポートしていないのを気にしているようだが、そういうのは es6-shim で対応できるし、新しい機能を使えることを優先して欲しいというのが個人的な感想…。
モジュール
現状は CommonJS っぽいモジュールを吐く。ES6 modules を吐いて欲しいという要望もあるが…
Emit ES6 modules · Issue #2574 · purescript/purescript
2017年4月時点での議論を斜め読みした感じでは、まだブラウザーや Node.js がネイティブ対応してない(transpile 必須になる)のを気にしているようである。2018年になったのだから状況は変わるだろうか…。
数値まわりに関して
このブログでは以前 Haskell の Num クラスに対する不満 というのを書いた。この観点で PureScript の数値型や型クラスはどうなっているか。
まず、数を表す型としては、(標準では) Int と Number の2つがある。
Int は気持ち的に32ビット整数なのだが、乗算が Math.imul じゃないので要注意である。Int 型が追加されたのは、2015年6月リリースの バージョン 0.7.0 でのことのようである。
Number は倍精度浮動小数点数である。Int 型が追加される前は数値型はこれしかなかったためにこの名前なのだろう。
やろうと思えば符号なし整数型 (Word?) や、単精度浮動小数点数型なども実装できるだろうが、ない。
Haskell では数値リテラル(整数、小数)は Num (fromInteger) や Fractional (fromRational) によって多相的な型がついているが、 PureScript は単相的である。つまり、整数リテラルは Int 型だし、小数リテラルは Number 型である。
多倍長整数、有理数、複素数などは標準ライブラリーには含まれていない。欲しかったら外部のライブラリーを取ってくることになる。
数に関するクラス階層
Haskell での Num と Fractional に相当するクラスは、
Semiring <= Ring <= CommutativeRing <= EuclideanRing <= Field
という階層に分かれている。このほかに DivisionRing というのもある。
それぞれのメソッドは
- Semiring: add (+), mul (*), zero, one
- Ring: sub (-)
- EuclideanRing: degree, div (/), mod
- DivisionRing: recip
である。
単項マイナスの negate はメソッドではなく、 Ring に対する自由関数となっている。また、 pow の類((^)
, (^^)
, (**)
など)はない。
さっき数値リテラルがどうのこうの書いたが、そもそも fromInteger, fromRational 相当のものがない。
Haskell では Floating クラスに sin, cos 等の一部の初等関数が定義されているが、 purescript-prelude にはそういう関数はない。purescript-math には sin, cos などがあるが、 Number -> Number の関数として単相的に定義されている。また、双曲線関数などは含まれない。
階層自体にケチをつけるなら、
- Semiring から add, zero が分離されていないので、ベクトル空間に適用しづらい。
- EuclideanRing がイケてない:
- 「体はユークリッド環である」という言明はもちろん真だが、果たしてユークリッド環としての操作を抽象化して嬉しい場面があるのか、筆者としては正直疑問である。
- もちろん、ユークリッドの互除法のようにユークリッド環に対して実装できるアルゴリズムはあるが、有理数係数多項式に対してそれをやると係数膨張が起こって大変なことになる。
- 体を実装するごとに、意味のない degree メソッドと mod メソッドを毎回書かされる羽目になる。「整数除算と体の除算を統一する」というメリットに見合うとは思えない。
- PureScript に EuclideanRing なるクラスが導入されたのは、おそらく このへん の議論が関係していると思う。
あたりだろうか。
エコシステム
パッケージの管理には bower (的なやつ?)を使う。pulp というやつもパッケージ管理と言って良いのだろうか。
バンドラーとしては webpack と rollup 用にそれぞれプラグインがあるようだが、 rollup 用のプラグインにインライン化などの最適化の機能があるためか、GitHub Issues での議論 を見る感じでは rollup の方が言及が多い印象を受ける。pulp や、 PureScript 本体(purs コマンド)にもバンドルする機能があるようだが、それぞれどういう位置づけなのかは筆者にはまだよくわからない。
型付きの altJS としては TypeScript が有力だが、 TypeScript と協調させるためのサポートは正直薄い:
- Generate TypeScript Definition Files · Issue #29 · purescript/purescript
- Ability to parse d.ts files as purescript bindings? · Issue #1216 · purescript/purescript
筆者がやっている既存の TypeScript のプロジェクトに部分的に PureScript を使おうとした(型クラスと演算子オーバーロードが目当て)が、かなり辛かった。
まとめ
言語機能と処理系(エコシステム)共に、まだ未熟で、今後の発展が期待される。
それでも、巨大なランタイムなしで Haskell ライクなコードを書きたい場合は、触れてみる価値はあると思う。