なぜTypeScript推しなのか

KibelaのフロントエンドをES2015からTypeScriptに絶賛移行中です。

www.typescriptlang.org

で、なぜ flow じゃないくてTSなのかって話です。

flow vs typescriptである理由は、どちらもJSのスーパーセットをうたう静的型付きのaltJSだからです。この時代にあえてaltJSを導入する理由としては静的型があるというのが必須で、かつ学習コストを考えるとJSのスーパーセットであるのが望ましいでしょう。

言語仕様

言語仕様の点から言うと、決定的な差はないと思っています。

メリットもだいたい同じで

  • 生産性: エディタの補完をJSよりも賢くできるので、より少ない脳のワーキングメモリでコードを書ける
  • 堅牢性: コンパイル時に(=多くのケースではエディタで)typoなどの間違いを検出できるのでバグを減らせる
  • 学習コスト: JSをベースにしており、実行もほぼJSなのでほぼモジュールシステムと型システムだけを学べばよい

検索しやすさ・コミュニケーションのしやすさ

検索しやすさは圧倒的にTypeScriptです。

flowは検索しにくいのはもちろんのこと、その名前の元になったと思われるflow analysis との混同が激しく、flowやTypeScriptの文脈で口頭で「flow解析が云々」といってもだいたいflowのことだと勘違いされて通じません。

ファイルタイプに対する誠実さ

flowの拡張子は .js、TypeScriptの拡張子は .ts です。

拡張子でファイルタイプを判断することの多いこの世界線においては、flowがJavaScriptと同じ拡張子で互換性のない言語として書かせるのは、ファイルタイプに対する誠実さに欠けているのではないかと思っています。

もちろんマーケティング的に「flowは言語ではない。flowはJavaScriptである」と主張することは、心理的な敷居を下げるという意味では一定の効果があるかもしれません。しかし、その辺にあるコードをJSだと思って眺めているとコードハイライトエンジンやエディタが構文エラーを指摘してくる、というのは気持ちのいい体験ではありません。

開発言語

flowはOCamlです。

TypeScriptはTypeScriptです(つまりセルフホスティング)。

TypeScript処理系をJS(あるいはTS)からライブラリとして使用できるというのは大きなメリットです。

まとめ

以上です。言語としては決定的な差はないものの、それをとりまく環境はTypeScriptのほうが好ましいと考えています。ゆえに私はTypeScriptを推します。

Herokuのreview-appsが "pr-predestroy" をサポートして外部リソースの掃除をできるようになっていた

devcenter.heroku.com

Herokuのreview-appsはたとえHerokuを使っていなくても非常に便利なものですが、PR削除時にS3やElasticsearchなど外部にホストしているリソースを掃除する方法がありませんでした。

ところが、最近は pr-predestroy hookが実装されたようで、外部リソースの掃除ができるようになったみたいですね。

ますます便利になりありがたい!

mitamaeでrecipeのロードエラー時にデバッグしやすくした

一部のプロジェクトでmitamae (itamae on mruby) を使ってるんですが、自分が書いているときはともかく他人が書いているmiamaeでrecipeのロードエラーが発生すると、mruby-ioレベルでもmitamaeレベルでもファイル名を出力してくれなくてこれはデバッグできないぞという状態でした。

問題は2つのレイヤーであって、

  • mruby-io がopenの失敗時にファイル名を出力しない
  • mitamaeのロガーは読み込んだレシピを実行するときはログを吐くが、ロードの前あるいは失敗時の処理がない

低レイヤーのmruby-ioはopenは失敗時にファイ名名を出力すべきだし、miamaeレイヤーでもレシピの読み込みは非常に重要なのでもっと詳細にログに出してほしいところです。なおmitamaeレベルでのレシピ読み込みエラーはopen由来とも限らないので、どちらか一方ではなく両方のレイヤーで対応するのが一番いいと思っています。

というわけで両方なおして mitamae v1.4.5 にしてもらいました。

See Also

AndroidにおけるJava8のサポート状況 2017年版

公式ドキュメントにありました。一言でまとめると、Android Oのpreviewが出た現在においても「Android N (API version 24)と同水準」となっています。

Use Java 8 language features | Android Studio

Android Studio 2.4 preview 4 (およびそれが要求するツールチェイン)の段階では、 desugar と呼ばれるツール(実体はAndroid Gradle PluginのTransform APIによるbytecode weaving tool)によって、一度javacでコンパイルしたバイトコードのJava8の言語機能(lambda, repating annotationsなど)をJava6水準のバイトコードに変換し、それをdxコマンドでdexにコンパイルするというプロセスを経るようです。このdesugarされたバイトコードは $subproject/build/intermediates/transforms/desugar/* にクラスファイルとして残されるので、どのようにバイトコードが変更されたかは確認できます。

この変換されたバイトコードをみるかぎり、Android O相当のAndroid SDKによってサポートされた「Java8の言語機能」はretrolambdaとほぼ同じです。

件のドキュメントの表にもあるとおり、 “Java8 Language API” つまりJava8の標準ライブラリ(stream, optional, etc.)を使うには依然としてAPI level 24が必要で、状況としては一年前のAndorid Nの時点とほぼ変わりません。

つまり、Android Oのpreviewが出ている現在でも、Java8に関して去年からの唯一のアップデートは、 retrolambda相当のツールがAndroid SDKに同梱されるようになった というだけということになります *1

Android Oで java.lang.invoke パッケージが追加されていたので、minSdkVersion = O ならもしかしたらinvokedynamicを使った本物のlambdaを使えるのではないかと思ったのですが、いまのところそのようにコンパイルされることは確認できませんでした。つまり、minSdkVersionの値に関わらず、desugarが必ず入るようです。このあたりはもしかしたら正規版までに変更があるかもしれませんが。

*1:去年の時点でβだったJackは正規版になるまえにdeprecatedになったのでノーカンで。

React Reduxファーストインプレッション

今更感ありますがReact Reduxを導入したの所感をメモしておきます。

github.com

  • ざっとみてこれなら自分でも再実装できそうだなという印象
  • いままではreact-micro-container でfluxしてた
  • React Reduxにすると、個々のreact componentをfluxフレームワークに依存しない形で設計できる
    • これに対して react-micro-container はcontainerに制御されることを意識した設計になる(=containerとcomponentで設計が異なる)
    • React ReduxなしでReactを初めてRect Reduxを導入するのは簡単だし、あとから別のflux実装にするのも簡単にできそう
    • アプリケーションに小さく導入することもそこまで難しくない
  • 個々のcomponentはシンプルに設計できていいが、Reduxとの繋ぎ込みでReact Reduxが魔法のように処理するところがやや多く、かつ繋ぎこみ用のマッピングが冗長な印象はある
  • 型がない素のJSだと繋ぎこみ部分がかなり不安。flowtypeなりTypeScriptなりは導入しないといずれ破綻しそう
  • ディレクトリ構成に悩む。まだ考え中

『Androidを支える技術』(I, II) の内容に興奮した

  • 『Androidを支える技術 I』 ~ 60fpsを達成するモダンなGUIシステム ~
  • 『Androidを支える技術 II』 ~ 真のマルチタスクに挑んだモバイルOSの心臓部 ~

これらを著者の有野さん よりご恵贈いただきました。ありがとうございます。

始めて知る内容も多かったのですが、既に知っていることでも著者の意見が反映されているのを読むと、いくつものモバイルOSを見てきたハッカーからみるとこう見えるのか!という新鮮な面白さがありました。

IとIIのテーマは独立しているので、どちらから読んでもいいと思います。

以下個人的に面白かった章をピックアップします。

I の見どころ

  • §1: ActivityThread.java にあるAndroidアプリのエントリポイント public static void main(String[] args) の役割
    • ActivityTheadはデバッグしてるとたまに見ることはあるものの全然役割を知らず、それどころかここに main() があるのすら知りませんでした
  • §3: Thread, Handler, Looperあたりの話
    • HandlerとLooperまわりは難解で、なんとなく表面的に知っているだけにとどまっていました。そのあたりを詳しく解説しているのは大変ありがたい…
  • §7: バイトコード実行系の話
    • Android 7.0でARTからVM + on-demand compileに戻ったのは初めて知った!たしかにART時代のAOT compileの遅さは気になっていたので、AOSPチームのその判断自体は納得できます

IIの見どころ

  • §3: OOM Killerとの戦いは涙なしには読めませんね…
  • §4: IIで一番驚いたはなんといってもinstance stateの保存(Bundle)に関して、「BundleがSystemServerのメモリ上に保持されている」という点です。そんな事がありえるのかと思うのですが、調べたかぎりだとそうとしか思えないとのこと
  • 本作はコラムが本体かってくらいの力作で、特にモバイルOSの歴史編(勝手に命名)はAndroid, iOS以前のモバイルOSの歴史を全然知らない私にとっては黎明期の壮大な物語です。こういうのが読みたかった。

なお『Androidを支える技術』レビュアーでありGoogleエンジニアでもあるomoさんのエッセイもすばらしいです。

死んでしまったOSたちへ – To Phantasien

MathJaxによる描画を細かく制御する

MathJax というのはLaTeXなどで書いた数式を美しく描画するための処理系です。 なおこのエントリは MathJax v2.7.0 を対象にしています。

www.mathjax.org

まず前提として、ほとんどのケースではMathJaxをconfigパラメータを与えてロードするだけで自動的にtypeset *1 が行われます。

この自動typesetは、HTML中のすべての要素に対してただ一度だけ行われるので、content editableでウェブエディタを実装しているケースやリアルタイムプレビューとの相性が非常に悪いのです。つまりcontent editable中のLaTeX記法は置き換えるべきではないのに置き換えてしまうし、プレビューが更新されてもtypesetされないのでこちらはLaTeX記法が処理されません。

そこで細かくMathJaxを制御する必要があるのですが、これがなかなか苦労したので、ハマったことや考えたことをここにメモしておきます。MathJaxは奥が深すぎて同じことをするのに何通りもの方法があり、ここに書いたものが唯一の方法ではありません。実際に実装するときは最新のドキュメントを参照することをおすすめします。

APIリファレンス

リファレンスはこちら。 ぐぐるとよく過去のバージョンのものが出てくることが多いです。何かを探すときは最新版をサイト内検索するのがいいでしょう。

http://docs.mathjax.org/en/latest/api/

SSRかCSRか

MathJax-node があるので、SSR: Server Side Renderingもできるようです。

MathJaxの描画処理はかなり時間がかかり、またpreviewとして仮描画したものを出したりするため、数式の描画を表示するときにかなりガクガクします。

SSRするとこのガクガクがおそらくなくなるので、可能であればSSRしたいところです。しかしそのためには専用のnodejsサーバを立てなければならず、ほかの(たとえばReact SSR)レンダリングサーバに相乗りできるならいいですが、そうではなくMathJaxのためだけに新規にnodejsサーバを立てるのであれば大抵の場合オーバースペックでしょう。

今回はCSR: Client Side Renderingにしました。

MathJaxのロード

MathJaxパッケージはnpmにもありますが、それをwebpackで固めるとextensionのロードが404になるという現象になり、結局解決できませんでした。

なのでcdnjsのようなpublic CDNを使うか、MathJax配布キットのとおりの構造でデプロイするかしないといけないようです。

MathJax 3.0でwebpack対応予定 ということなので、webpackに組み込みたいときは3.0を待つのがいいようです。

自動typesetを無効にする

configに skipStartupTypeset: true を渡します。

MathJax.Hub を設定するためのドキュメントはこちら:

http://docs.mathjax.org/en/latest/options/hub.html

MathJaxの初期化終了コールバック

MathJaxは非同期でコンポーネントをロードするので、 MathJax オブジェクトが存在するからといって使用可能とは限りません。

MathJax.isReady は一つの指標としては使えますが、初期化の終了イベントをフックしたいところです。

初期化シーケンスは Startup Sequence にあり、それぞれのタイイングでコールバックを呼ぶのでイベントリスナを登録すればよさそうです。

このシーケンスの詳細は省きますが、End が初期終了時に呼ばれるイベントです。

MathJax.Hub.Register.StartupHook('End', () => {
  // MathJax APIがすべて利用可能になった!
});

MathJax Typeset API

MathJax.Hub.Typeset(target, callback) を使うと、target elementを指定してtypesetを起動できます。

target は HTML element またはその配列、あるいは null で、 nullの場合は document.body 全体が処理の対象になります。

MathJaxは処理をシーケンシャルにするために MathJax.Callback.Queue というjob queueを持っているので、typesetの処理は実際にはこのようになります *2

MathJax.Callback.Queue(() => {
  MathJax.Hub.Typeset(elements, () => {
    console.log(`typeset is done for ${elements.length} elements.`);
  });
});

エラーハンドリング

プロダクション環境ではエラーがあってもサービス提供者としては何もでないことのほうが多いでしょうが、開発中はエラーの詳細を知りたいというケースでは “Math Processing Error” イベントをフックすることでコンソールにエラーの詳細を出力できます。

MathJax.Hub.Register.MessageHook("Math Processing Error", (message) => {
  console.error(message);
});

MathJax Preview

LaTeXをちゃんとレンダリングするのはかなり時間がかかるようで、デフォルトだと本描画のまえに数回プレビュー描画が入ります。

このpreviewは様々なレイヤでやっているらしく制御の仕方がはっきりしないのですが、 MathJax_Preview CSS classが指定されているので、これに対してスタイルを当てると見た目はそれなりに制御できます。

全体像

というわけで全体像です。実際に運用しているコードそのものではありませんが、だいたいこんな感じです。 requestTypeset() を呼ぶと (1) MathJaxがロードされていなければロードする (2) MathJaxが使えるようになるまで待ってtypesetを起動する (3) requestTypset() は呼ぶたびにtypesetを起動する、ということを行うコードです。

const CONFIG = 'TeX-AMS-MML_HTMLorMML'; // 適当なconfig

const DEV = (process.env.NODE_ENV != 'production');

let isLoading = false;

function loadMathJax(onComplete) {
  const script = document.createElement('script');
  // from https://cdnjs.com/libraries/mathjax
  script.src = `https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=${CONFIG}`;
  script.async = true;
  script.onload = () => {
    const MathJax = window.MathJax;
    console.log(`loaded MathJax/${MathJax.version} (${CONFIG})`);

    MathJax.Hub.Config({
      messageStyle: DEV ? "normal" : "none", // 左下に出るメッセージボックスは開発時のみ有効にする
      skipStartupTypeset: true,
      tex2jax: {
        inlineMath: [
          ['$','$'],
        ],
        displayMath: [
          ['$$', '$$'],
        ],
        processEscapes: true,
      },
    });
    MathJax.Hub.Register.MessageHook("Math Processing Error", (message) => {
      console.error(message);
    });
    MathJax.Hub.Register.StartupHook('End', onComplete);
  };
  document.head.appendChild(script);
}

export function triggerTypeset() {
  const elements = [...document.querySelectorAll('.content')];

  const MathJax = window.MathJax;
  console.assert(MathJax && MathJax.isReady);

  MathJax.Callback.Queue(() => {
    MathJax.Hub.Typeset(elements, () => {
      console.log(`typeset is done for ${elements.length} elements.`);
    });
  });
}

export function requestTypeset() {
  if (!document.querySelector('.content')) {
    return;
  }

  if (window.MathJax && window.MathJax.isReady) {
    triggerTypeset();
  } else {
    if (isLoading) {
      return;
    }
    isLoading = true;

    loadMathJax(() => {
      triggerTypeset();
    });
  }
}

*1:MathJaxの描画処理のことで、原義は「活字を組む」という意味です。MathJaxでこの用語を使うのはTeXに由来すると思われます。

*2:queueを使う必要はもしかしたらないのかもしれませんが、MathJaxは内部で様々なイベントが飛び交う複雑な非同期システムなのでこのようにするのが無難でしょう。

yarnpkgのdependenciesにgithubのリポジトリを直接書こうとしてハマった

追記:(2017/04/14): "foo" : "gfx/foo"$github_user/$repository)というフォーマットでうまくいきました。ε-(´∀`*)ホッ


追記(2017/04/13): 結局解決できてませんでした。つらい。

fooというnpm moduleにパッチを当てた gfx/foo という版をどうしてもいま使いたい、というときに、package.jsonにはこんな感じにURLを直接書けるはずということになっているのですが

{
  "dependencies": {
    "foo": "git+https://github.com/gfx/foo#branch",
  }
}

これがどうもCircleCI上のyarnpkgだと刺さってしまって動きませんでした。yarnpkgのアップグレードなどでも解決せず。結局以下のように .git suffixを付けると解決できました。

{
  "dependencies": {
    "foo": "git+https://github.com/gfx/foo.git#branch",
  }
}

CommonMarkの現状 2017年4月版

CommonMarkというのはMarkdownの標準化を目指して2014年に立ち上がったプロジェクトです。

当時のニュース:

Standard Markdown Becomes Common Markdown then CommonMark (日本語版)

公式サイト:

http://commonmark.org/

あれから3年、とうとうGitHubがCommonMarkを採用したというアナウンスがありました。つまり、長らくMarkdownデファクトと考えられてきたGFM: GitHub Flavored Markdownは、いまやCommonMarkのスーパーセットなのです。

A formal spec for GitHub Flavored Markdown | GitHub Engineering (2017/03/14)

To ensure that the rendered Markdown in our website is fully compliant with the CommonMark spec, the new backend implementation for GFM parsing on GitHub is based on cmark,

実際には、このエントリにあるように、GitHubはオリジナルの参照実装であるcmarkをforkしたもの(cmark-gfm)を使っています。これは、GFMのいくつかの拡張について仕様が定まっていないためで、それらの議論が終わったらオリジナルのcmarkにマージするつもりだとのこと。GitHubはCommonMarkのコアメンバーなので、このあたりについては心配はいらなそうです。

https://github.com/github/cmark

拡張: https://github.com/github/cmark/tree/master/extensions

Ruby gemの CommonMarker も、実際に参照しているのは github/cmark で、いまはGitHub社のkivikakk氏がcommonmarkerとgithub/cmark両方をメンテしているようですね。

このGitHubのアナウンスをうけて、国内外のMarkdown採用サービスが CommonMark (あるいはその拡張のGFM)に移行してゆくと考えられます。

Kibela的にはCommonMarkの拡張構文への関心が高いところです。すでにPlantUMLとLaTeXという二種類の記法を描画するためにMarkdown構文を拡張しているからです。

blog.kibe.la

blog.kibe.la

CommonMarkの仕様や拡張については talk.commonmark.org というフォーラムがあるので、CommonMarkの将来についてはこのあたりを眺めるとよさそうですね。

https://talk.commonmark.org/

JavaScript SSRのためのレンダリングサーバがほしい

いまReactのSSRのためにHypernova を使っていて、これはこれでちゃんと動くんですがいくつか不満があります。

  • Unix Domain Socketをサポートしていない
  • アプリケーションサーバ(express)の設定をほとんど変えられない
    • worker数とか
    • bodyの圧縮にzstdを使うとか

さらに、JSでレンダリングしたいコンテンツは他にもあって、たとえば

  • MathJax
  • Markdown
    • CommonMarkだとC実装とJS実装があるのでめちゃくちゃ頑張らなくても同じようなものを作ることはできるが…

なのでさしあたってこの三種類のコンテンツをレンダリングするためのNodeJSサーバがあるといいなと。

ここまでくるとNodeJSでアプリケーションサーバを書いたほうがいい気もしますが、まあそこは既存の資産があるのでステートレスなレンダリングだけでもまずはNodeJSでやれればなあと思いました。

というか、こうなるとHTTPサーバである必要もなさそう。