静的型付けでnull安全なAltJSの評価と選定
2019年09月版. 静的型付けでnull安全なAltJSの評価と選定.
比較整理する必要があったので比較しました. 間違っている所などの指摘を受けたいので広く公開します.
ちなみに私は2015年頃はDartを推していたようですね.
当時のTypeScriptはstrictNullChecks
も@types
も無かったのであまり魅力的に感じなかったようです.
私の提言
結論から書きます. 個人的にはPureScriptを推したいですが, 流石に他の人にとっては学習コストが大きすぎると思ったので, 基本的に以下のような感じに選ぶと良いと思いました.
- 外部ライブラリの使用がメッセージ単位でまとまる程度に収まる → Elm
- Reactを使いたい → Reason
- VueやAngularなどReact以外のフレームワークをガッツリ使う, Node.jsをサーバーサイドでメインで使う, 小規模なスクリプト → TypeScript
私1人で開発する場合や周りの人間がみんなHaskellわかっている場合はPureScriptを使いたいですね.
プログラミング言語を選択するということについての私の考え
各プログラミング言語を評価する前に私のプログラミング言語の選択に関する考え方を書いておきたいと思います.
ついこの前「プログラミング言語はどれも似たように見えるし出来ることは同じなのに何故沢山あるの?」 と聞かれました. 私は「もちろん他にも色々理由があるけど『出来ないことを作る』ことが必要なので新しい言語が産まれるんだと思う」 と答えました.
例えばC言語は長さ6の配列の7番目のメモリにアクセスすることが出来ます. エラーにはならずにメモリ領域を読み取れます. よって手持ちポケモンの7体目の情報を参照してミュウが出てくるなどの謎の現象が起きるわけです. (ポケモンを例に説明をしていました)
JavaやD言語では配列は長さの情報を持っているので範囲外の添字はエラーになります. しかしそれを行うと配列の長さをメモリ上に保持しておくので使用メモリは増えて, アクセス前のチェックというCPUオーバーヘッドが産まれます. しかしそれによってだいぶ安全になります.
これは「出来ないことを増やす」ことで改善した例です. 他にも静的型付けやnull安全性や副作用の分離も「出来ないことを増やす」ことです.
C++のテンプレートも一見「出来ることを増やす」ように見えますが, なるべくvoidポインタを使わせないようにしているので「出来ないことを増やす」方面の拡張と見ることも出来ます.
主に低レイヤーのプログラミングでC言語は広く使われています. しかしC言語で書かれたソフトウェアはしばしば脆弱性騒動を引き起こしています.
生のJavaScriptで書かれたソフトウェアもバグを大量に産んで生産性を阻害しています.
真の生産性のためにはアプリケーションを作るのに障壁にならない程度の制約を持ったプログラミング言語を選ぶべきです.
万能ナイフを求めてもそのナイフは自分をも切ってしまいます. 過剰な権限は自分の足を撃つことに繋がります. 適度な制約は生産性を高めます.
多少学習コストがかかるとしてもプログラミング言語は用途によって分けるべきだと考えています.
TypeScript
TypeScript - JavaScript that scales.
- Microsoft製
- でもApache 2 Licenseなので割と安心
- 学習コスト: 数ある静的型付けAltJSの中でもかなり楽
- シンタックス: 下手なbabelつけたJavaScriptよりしょぼい程度
- 基本的に新しいシンタックスはEcmaScriptのstage 4にならないと取り込まれないため
- babelを併用するという手も無くはないですが割と面倒
- 実行時安全性: 低い
- ビルド成功してるのにpolyfillライブラリ未読み込みでランタイムエラーが出ることが割とある
any
やas
とかの抜け道が結構ある- 不変性を型で保証するのが面倒
- 型の暗黙変換とかはそのまま
"1" + 1
や"1" + {}
が--strict
付けても警告なしにコンパイルされるのはつらい
- 型の表現力: 低い.Mapped typesとかは面白いですが代数的データ型や型クラスがないとやはりつらい.
- 型推論: 低い.書かないと独立した関数の引数はだいたい
any
になってしまう.クロージャだと多少は効く. - ライブラリの型定義の充実度: AltJSの中でも最大級に多い
- npmで
@types
で検索したら1311件.@types
ではなく直にTypeScriptをサポートしてるパッケージもあるので更に増える.
- npmで
- JavaScriptとの接続性: AllowJSがあるぐらい雑に接続できる.最悪全部
any
にしてしまえば接続できますし - 開発環境の充実度: AltJSの中でも一番高いのでは
- 出力するJavaScriptの可読性: 基本的に型チェックするだけなのでES5にターゲットを絞るとかしてなければほぼ同じものが出てくるはず
- フレームワークのサポート: React, Vue, Angularで一級市民扱い
- AssemblyScript: ロジックは単純ですが重い計算をWebAssemblyで高速化するには良いのかもしれません
- GCもRAIIも無いのが辛すぎるので少しでも複雑になったらRustとか使った方が良さそう
Flow
Flow: A Static Type Checker for JavaScript
- Facebook製
- 学習コスト: TypeScriptとほぼ同じ
- シンタックス: TypeScriptとほぼ同じですがbabel使うことを前提にしてる節があるので連携はしやすい
- 実行時安全性: 低い
- 抜け道がいくらでもある
- 不変性を型で保証するのが面倒
"1" + {}
はエラー出ますけど"1" + 1
は出ない.つらい.
- 型の表現力: TypeScriptとほぼ同じ
- opaque typeは
newtype
パターンを提供しつつも自分のモジュールでは同じ型として扱えて面白い
- opaque typeは
- 型推論: TypeScriptよりは多少頑張って推論してくれる
function mul(x, y) { return x * y; }
はTypeScriptだとstrict
付きだとエラー,strict
無しだとany
, Flowはnumber
だと自動推論する.
- ライブラリの型定義の充実度: TypeScriptよりは劣る
- 現在flow-typed/definitions/npm at master · flow-typed/flow-typedの数は同じライブラリのバージョン違い含めて711
- FlowにもReactなどネイティブ対応しているものはありますが…
- JavaScriptとの接続性: 型つけただけなので相当簡単
- 出力するJavaScriptの可読性: これは本当に型チェックするだけなのでそのまま出てきます.バックポートもbabelに頼ってるのでFlow自体は改変しません.
- 開発環境の充実度: Facebookが割と頑張ってた
- フレームワークのサポート: TypeScriptより基本的に弱い
- React系は内部実装がFlowなので一級市民ですが他からはあんまり気にかけてくれてない
- 他Immutable.jsのようなFacebookスタックに載るならFlowの方が少し優遇はされてる気がします
- TypeScriptで良いのでは?
- もはやReact NativeもTypeScript使えるようになりましたし
- 言語パワーがTypeScriptとほぼ同等にしか見えないのでサポートが強いTypeScriptに流れてしまう
- 頑張ってFlow使う理由探したけど大きなものは見つかりませんでした…
- TypeScript(10文字)よりFlow(4文字)の方が短いからタイプは楽
- FlowType(8文字)でもまだ短い
- 型構文を付けるという明白なシンタックス拡張をしているのに拡張子が
js
なのが気に入らない
Reason
Reason · Reason を使うと、JavaScript & OCaml の両方のエコシステムを活用しながら、単純、高速かつ高品質な型安全コードを書くことができます。
- Facebook製
- ReasonなのかReasonMLなのかはっきりしない
- AltJSというよりAltOCaml(Reason→OCaml→JavaScriptとコンパイルされるため)
- 私が書いた文章読むよりWhat & Why · Reason見た方が手っ取り早いかもしれません
- 学習コスト:
- AltOCamlとは言ってもシンタックスは相当JavaScriptに寄せてきているので全然苦労しなさそう
- セマンティクスもOCaml自体かなりシンプル(要出典)なので大して難しくない
- 実際Reason全然知らないOCamlも大して知らない状態でもシンプルなTODOアプリは5分で書けました.Reasonによる単純todoアプリ
- シンタックスの表現力: そこそこ高い
- 個人的にOCamlの混乱の原因である
begin
や多重セミコロンが消えてるのはポイント高い - JavaScriptに寄せてきてると言ってもちゃんと
switch
やif
は式です - 関数呼び出しに括弧がいるのが推奨されているのは寄せてるから仕方がない.
- 個人的にOCamlの混乱の原因である
- 実行時安全性: 高い
immutable
がデフォルトになっていて型チェックもデフォルトで厳しい- でも純粋関数型言語じゃないので副作用は自在に起こせます
- 型の表現力: 代数的データ型があって
switch
でパターンマッチがあるのはとても嬉しい.型クラスがないから関数呼び出しは少々冗長になりますが…- 多相バリアントを押し出してないですが実はあるらしいReasonML: polymorphic variant types
- 型推論: OCamlなので当然最強.型注釈なんて忘れてもちゃんと型が付いてる.JavaScript呼び出したら流石に例外ですが.
- ライブラリの型定義の充実度: 意外とある.redex | Reason Package Indexを見ると有名所は割とあります.
- JavaScriptとの接続性: 意外と楽.雑にインラインで呼び出したり型を着けたり出来ます.Interop · Reason
- 出力するJavaScriptの可読性: 意外と高い.BuckleScript/bucklescript: A backend for the OCaml compiler which emits JavaScript.は十分読めるレベルのJavaScriptを吐き出してくれます.
- なぜかJavaScriptをOCamlにコンパイルするツールがあるので相性が良いのかもしれません facebookarchive/JSCaml: A compile time transformation from JavaScript to OCaml, along with an OCaml implementation of the JavaScript builtin library.
- 開発環境の充実度: コマンドラインツールが揃っていてwebpackの設定を自動的に書くなど割と現代的
- コードフォーマッタもREPLもちゃんとあります
- フレームワークのサポート: Reactなら十分実用的
- ReasonReact · All your ReactJS knowledge, codified.は実用的レベルだと思います
- React Hooksによって普通の関数によって状態などを扱えるようになったのも追い風.これまではRecordを返して
class
を模倣していました- OCamlの
class
は一切使われてませんでした.相変わらず不憫.オブジェクトは OCaml の鬼子 - camlspotter’s blog
- OCamlの
- Reduxみたいにglobal stateを扱いたい場合Context APIを使うらしいですね Current state of React context - General & Questions - ReasonML Forums
- Reduxの移植もありますが「これ必要ないと思いますよ」ってREADMEに書いてあります reasonml-community/reductive: Redux in Reason
- OCamlへの変換なのでネイティブコードに変換できます
- Oni 2に使われる予定の「Electron的なものの上でネイティブコードを動かせば動作速度も生産性も早くて強いのでは」というReveryというフレームワークがあります
- サーバーサイドも出来るかもしれない(LSP実装とかでやってる人はいます)
- でも基本的にこの環境ではOCamlじゃなくてNode.jsのフレームワークを使いたがる人が多いはずなのでそうなるとasyncが実装されてないと面倒そう
- JavaScriptだけではなくopamにあるOCamlのライブラリも使える時があります
- async/awaitの実装は検討中(Promiseは当然ある)Syntax proposal: async/await · Issue #1321 · facebook/reason
- BuckleScriptそのまま使っても良いのではとも思いましたがそこはJavaScriptプログラマに対する譲歩ですね
Elm
Elm - A delightful language for reliable webapps
- 独立系開発
- webフロントエンドに特化した言語
- サーバサイドは現状無理
- The Elm Architecture(TEA)はReduxの元ネタ
- 学習コスト:
- syntaxがJavaScriptに全く似ていないので一見難しそう
- でも実はシンプルなので簡単です
- シンタックスの表現力: 高い
- ML系(Haskell系)の関数の構文は最高で, 普通の関数呼び出しの形式なのに閉じタグとか気にしないといけないJSXより快適です
- 実行時安全性: 極めて高い.副作用は全部TEAによって扱われます.実行時例外が存在しないように作られています.
- 型の表現力: そこそこ.代数的データ型がある.型クラスは無いです.フロントエンド特化なら型クラス無くても別に気にならないという声が多数あります.
- 型推論: 強く推論してくれますが, 型がドキュメントになるという思想があるのでトップレベルには書いた方が良いでしょう.
- ライブラリの型定義の充実度: なんでもElm独自に用意しようという姿勢が見えます.それは接続性の特徴から来てるのでしょう.Elm Packagesには結構パッケージはあります.
- JavaScriptとの接続性: 面倒
- ElmはJavaScriptをFFIで呼び出すのではなく, portというJavaScriptの世界をサーバとして捉えて非同期にメッセージをやり取りする仕組みを採用しています
- しかもそのportですらライブラリでは使えません
- Elm内部で実行時エラーを絶対に起こしたくないのでこういう設計になっています
- JavaScriptコードはサーバとして動かす必要があるのでその部分をTypeScriptで書く方法もありますdillonkearns/elm-typescript-interop: Generate TypeScript declaration files for your elm ports!
- 出力するJavaScriptの可読性: サイズこそ小さいですが流石に読めない
- 開発環境の充実度: 実用性に重きを置いているのかREPLもフォーマッタもプロジェクト作成ツールもLSPサーバも揃ってます
- フレームワークのサポート: Elm自体がフレームワーク
- React相当の仮想DOMを持っている
- Reduxの元ネタのTEAを持っている
- React Router相当の機能が
application
関数にある - CSSフレームワークにはelm-uiがよく使われていて, 他にマテリアルデザインのフレームワークもあります
- CSS in JSはスタイル適用がそもそも関数で, elm-cssを使えば型安全にCSSを書くことも出来ます
- このようにReactを使う場合と違って選定に悩む必要がないというのが強み
PureScript
- 独立系開発
- HaskellよりHaskellらしい言語
- 学習コスト: 高い.シンタックスはほぼHaskellそのものの上extensible effectsが積み重なってくる.
- シンタックスの表現力: 極めて高い.Haskellよりレコードのフィールド構文が柔軟.名前が被ってても問題なし.
- 実行時安全性: Elmに劣る程度.FFIがあるから仕方がない.副作用は型で分類できます.
- 型の表現力: 代数的データ型あり, 型クラスあり, 拡張可能作用を持っているので多分多相バリアントの力もある
- 型推論: 型の力が強すぎるのでせめてトップレベルには注釈が必要です
- ライブラリの型定義の充実度: Pursuitを見る限りそんなに多くはない
- JavaScriptとの接続性: 少し面倒ですがFFIがあるのでElmよりは楽.Reasonよりは面倒.
- 出力するJavaScriptの可読性: 意外なことに可読性に気を使っていて読めるコードが出てきます.型インスタンスにも名前をつけないといけなかったりするのもこれのためらしいですね.
- 開発環境の充実度: 良くはなかったのですが最近spagoというモダンなビルドパッケージシステムが出てきてたりマシになってきました.
- フレームワークのサポート:
- halogenが仮想DOMにグローバル状態保存も出来ます
- ルーティングにはnatefaubion/purescript-routing-duplex: Unified parsing and printing for routes in PureScriptなどがあるようです
- 何故かC++やErlangにコンパイルできます
GHCJS
開発環境のビルドすら安定しないものはプロダクションレディではないと思います. nightlyが取れてから検討ですね.
Rust
同じくnightlyが取れてから検討です.
Scala.js
- 学習コスト: Scala全く知らない人には高い
- シンタックスの表現力: 高い.Scala 3になれば更に強力になるのですがScala 2でも十分強い.
- 実行時安全性: それなり.Reasonと同程度では.
- 型の表現力: 代数的データ型あり, 型クラスあり.
- 型推論: カリー化しないと型推論効かなかったり奇妙なところがある(Scala 3になると改善されるらしい)
- ライブラリの型定義の充実度: sjrd/scala-js-ts-importer: TypeScript Importer for Scala.jsによってTypeScriptの型定義ファイルをある程度持ってこれるらしい
- JavaScriptとの接続性: 型付けすることも
js.Dynamic.global
で雑に呼び出すことも出来るらしい.インラインはないようですね. - 出力するJavaScriptの可読性: それなりに読めるらしい
- 開発環境の充実度: Scalaのものがある程度流用可能なのでそれなりに充実していそう
- フレームワークのサポート: Reactが普通に使えるらしいSlinky 0.6.0: The One With Hooks - Shadaj Laddad - Medium
- Scalaのライブラリも一部使える
- サーバーサイドをScala.jsで書いてNodeで動かす人が居るらしい
- 一応実用範囲なのでサーバーサイドもScalaなら色々統一出来るのでアリでは?
Kotlin/JS
Kotlin to JavaScript - Kotlin Programming Language
Scalaと同じ. Androidアプリや ktorio/ktor: Framework for quickly creating connected applications in Kotlin with minimal effort のようなサーバサイドとライブラリ共有するなら使える?
Dart 2
Dart programming language | Dart
Flutter - Beautiful native apps in record time はとても興味深いですがまだnull安全じゃないので検討対象外です. Sound non-nullable types with incremental migration (NNBD) · Issue #110 · dart-lang/language
Dartは何故かよく見捨てられた言語扱いされてますけど, Google AdWords, AdSense, AdMob, Assistantと言ったGoogleの強い収益源に採用されてる言語がそんなに簡単に見捨てられることはないと思います.
AngularDart | angulardart.dev はAngular使うことになったとしてもnull安全なTypeScript使った方が良さそうですね.