Kotlin の JavaScript サポートを試す

Kotlin 1.1 で JavaScript がサポートされたので、Your first Node.js app with Kotlin – Miquel Beltran – Medium を読みながら試してみました。仕事で JavaScript ばかり書いている背景があるので、気分転換で一見 JavaScript っぽくない別言語 で Node で動くサーバーを書いてみたかったという気持ちです。

GitHub - kitak-sandbox/node-with-kotlin

試してみて

Tips っぽいやつ。

実行時エラーのスタックトレースが変換後の JS で出てくると分かりづらいので、GitHub - evanw/node-source-map-support: Adds source map support to node.js (for stack traces) で .kt の行数でスタックトレースを出力させるようにしました。パッケージをインストールして、require するだけ( https://github.com/kitak-sandbox/node-with-kotlin/blob/89f32b5e1de76a374ad8d5595fb6af4890a47f0d/src/main/kotlin/Main.kt#L5 )です。

サードパーティライブラリの型付けを試みるために、TypeScript の型定義ファイルを Kotlin の定義に変換するツール( https://github.com/Kotlin/ts2kt )を使ってみたんですが、今のところ、TypeScript の型定義ファイルを完全に変換できるわけではなさそうで、ライブラリによってはエラーや警告がそれなりに出ていました。ツールに関してはもう少し成熟するのを待って、静的型付けが必要と感じた場合は都度 Kotlin で定義を追加するのが良さそうです。

現場で使う?

お仕事で使うかどうかという話。単純に JavaScript に静的型付けや null 安全性を求めるのであれば、TypeScript で書いたほうが良いと思います。JavaScript + 静的型付けな言語で、普段、JavaScript を書いているのであればとっつきやすいですし、エコシステムも成熟しているというのがその理由です。

それでも、JavaScript をターゲットとして、Kotlin を採用すべき場面があるかどうか。バックエンドが Kotlin で書かれていて、コードをクライアントサイド(ブラウザ)やバックエンドフロントエンド(サーバー)で共有したいときに検討する価値があるかもしれません。
Kotlin ではないですが、Scala.js でそういった試みをおこなった事例があります( 0-9, 二槽式とはなにか )。 とはいえ、記事の終わりの方にもあるように、採用すべき場面はかなり限定される気がします。

とりあえずは、始めに書いたように個人的に JavaScript で書いているアレコレを Kotlin の書き味で楽しむぐらいがちょうどいいかな、という気持ちです。

シンプルな PaaS 「Now」で Go で書いたアプリのデプロイを試した

けっこう前に存在を知ったんですが、全然触っていなかったので試してみました。

Now 自体の説明は、公式サイト( Now – Realtime global deployments )や中の人の翻訳( http://qiita.com/nkzawa/items/8bf62549f79ebbcaafd8 )を読むのがいいと思います。

Now を運営している Zeit は、Next.js を開発していたり、Socket.IO の開発で有名な @rauchg が所属していて、そういった点でも興味を引きますね。 Now は、Node.js と Docker で動作するアプリのデプロイに対応しています。

今回は Go で書いたアプリを Docker をターゲットにデプロイしてみます。デプロイは、アプリのリポジトリに Dockerfile を用意して、now コマンドを叩くだけです。

事前にバイナリをビルドする

最初、golangをDockerでデプロイする — そこはかとなく書くよん。 のノリで事前にバイナリをビルド( GOOS=linux GOARCH=amd64 go build )して、Dockerfile の COPY でバイナリを追加する形でのデプロイを試みました。

こんなかんじです。

FROM scratch
LABEL name "hello"
COPY hello /hello
EXPOSE 80
ENTRYPOINT ["/hello"]

ただ、Go が出力するバイナリは helloworld 程度の内容でも 1 MB を超えるので(バイナリのサイズをめっちゃ減らす術があったら教えてください)、無料の OSS プランの最大ファイル容量( refs: ZEIT – Now Pricing )にひっかかってデプロイできませんでした。とぼとぼ。 ひとつ上の Premium プランであれば、問題なくデプロイできそうです。

デプロイの過程でビルドする

月 $15 は、お遊びで使うにはちょっと高いので、デプロイの過程でビルドさせることにしました。Dockerfile はこんなかんじです。

FROM golang:1.8
LABEL name "hello"
WORKDIR /go/src/app
COPY . .
RUN go-wrapper download
RUN go-wrapper install
EXPOSE 80
ENTRYPOINT ["go-wrapper", "run"]

使ってみた感想として、now コマンドを叩くだけでデプロイできる( git を使う必要すらない )というところで、非常にお手軽です。 アプリが Now の固有の仕様に引きずられない、ロックインされないのも好印象でした。特に Docker の上で動かすことができれば、他の Docker に対応している PaaS への移行も容易なので気持ちが楽です。 ちょっとした静的サイトをサクッと公開したり、プレゼンのデモアプリや趣味アプリを動かすサービスとして、有力な選択肢のひとつではないでしょうか。

デヴィッド・リンチ監督作品 ストレイト・ストーリー を観た

観た。脳卒中で倒れた兄に会いにトラクターで560kmを移動したアルヴィン・ストレイトというおじいさんの実話を基に作られた映画。

ストレイト・ストーリー リストア版 [Blu-ray]

ストレイト・ストーリー リストア版 [Blu-ray]

アルヴィンを演じるリチャード・ファーンズワースの輝きに満ちた目、透明感のある声で発される演技は、まるで本人が演じているのではないか、と思えるぐらい役にハマっている。
道行く先々で出会った人々との交流で、アルヴィンの頑固な性格が出てくるのだけど、「あぁ、こういう頑固なおじいさんいるよね! でも、こういう頑固さ、嫌いじゃない。真っ直ぐだ」という気持ちになる。

音楽はデヴィッド・リンチと多くの作品でタッグを組んでいるアンジェロ・バダラメンティ。全体を通して、アルヴィンを表現しているような清廉な音楽が流れていて、これがとても素晴らしい。サウンド・トラックを買いたい。

兄のライルを演じるのは、TWIN PEAKS Fire Walk With Me でも印象的な演技をしていたハリー・ディーン・スタントン。兄と再会するシーンは最後の数分でセリフもほとんどないのだけど、このシーンのアルヴィンとライルの顔の表情や息遣い、声の出し方、そのひとつひとつから目が離せない。
ライルは、弟のトラクターに目線をやった後、徐々に目に光るものが溜まっていく。言葉は少ないけれども「遠いところからあんなものでわざわざ俺に会いに来てくれたんだなぁ。あぁ、泣きそうだ。だが、弟の前で泣くのはみっともない、我慢だ」という心の声が顔の表情だけで伝わってくる。
アルヴィンも数十年確執のあった兄との再会で、確執のあった人物と邂逅するときに誰もがそうなりそうな、少し居心地が悪そうな、目線を兄の顔からさりげなく外した絶妙な演技をしている。 その後、ふたりが目線を上に移した後、星いっぱいの夜空に映像が切り替わり、ふたりがこれからどんな話をするのか観ている人間に想像を膨らませてのエンディングロールとなる。

全体的に話の進み方はゆっくりで、正直途中でダレそうにもなるのだけど、観終わった後になんとも言えない余韻が残る。この余韻を楽しむために、定期的に観ることになりそうだ。

雪花の虎 を読んだ

読んだ。

上杉謙信が女性だったら、そんな大胆な仮説に基いて書かれた漫画。登場する資料だけでは、実際に女性だったとする根拠としては薄いけれど、漫画の設定としては面白い。

浦沢直樹の漫勉の東村アキコ回( 東村アキコ | 浦沢直樹の漫勉 | NHK )では、この作品を扱っているので、見た後に読むと「あー、あのとき描き直していたやつだ」「あのとき一発描きしていたやつだ」みたいな楽しみ方ができるのも良かった。

RxJS を使って、Vuex のようなものを書いた

散歩していたときにふと思いついたので書いてみた。

Vuex のゲッター、Vue の算出プロパティの実装は Observer パターンが適用されているので、Observer パターンが根底にある Rx で上手く実装できるんじゃないかな、という思いつきだったんですが、思いの外、簡単にできました。 ステート、算出プロパティ(ゲッター)、アクション、ミューテーションを一通り実装していますが、アイディアの検証レベルで書いたものなので、算出プロパティの依存関係を明示的に指定しないといけなかったり等、細かいところは色々違っています。

const Rx = require('rx-lite')

class ReactiveProperty {
    constructor(state, name, initialValue) {
        this.subject = new Rx.BehaviorSubject(initialValue);
        this.changed = this.subject.skip(1);

        Object.defineProperty(state, name, {
            get: () => {
                return this.subject.getValue();
            },
            set: (val) => {
                this.subject.onNext(val);
            }
        });
    }
}

class ComputedProperty {
    constructor(store, name, dependencies, getter) {
        this.subject = new Rx.BehaviorSubject(getter(store));
        this.changed = this.subject.skip(1);

        Rx.Observable.combineLatest.apply(null, dependencies.map(function (dep) {
            return dep.changed;
        })).subscribe(() => {
            this.subject.onNext(getter(store.state));
        });

        Object.defineProperty(store.computed, name, {
            get: () => {
                return this.subject.getValue();
            }
        });
    }
}

class Store {
    constructor(options) {
        this.state = {};
        this.computed = {};
        this._actions = options.actions;
        this._mutations = options.mutations;

        this._properties = {};

        for (let name in options.state) {
            this._properties[name] = new ReactiveProperty(this.state, name, options.state[name]);
        }

        for (let name in options.computed) {
            let computed = options.computed[name];
            let dependencies = computed.slice(0, computed.length - 1).map((name) => {
                return this._properties[name];
            });
            let getter = computed[computed.length - 1];
            this._properties[name] = new ComputedProperty(this, name, dependencies, getter);
        }
    }
    dispatch(action, payload) {
        return new Promise(() => {
            return this._actions[action].call(this, {
                commit: this.commit.bind(this),
                state: this.state
            }, payload)
        });
    }
    commit(mutation, payload) {
        return this._mutations[mutation].call(this, this.state, payload);
    }
    watch(name, callback) {
        return this._properties[name].changed.subscribe(callback);
    }
}

const store = new Store({
    state: {
        firstName: 'John',
        lastName: 'Smith',
        amount: 10
    },
    computed: {
        fullName: ['firstName', 'lastName', function (state) {
            return state.firstName + ' ' + state.lastName;
        }]
    },
    actions: {
        increment: function (context) {
            return new Promise(function (resolve) {
                setTimeout(function () {
                    context.commit('increment');
                    resolve();
                }, 1000);
            });
        }
    },
    mutations: {
        increment: function (state) {
            state.amount += 1;
        }
    },
});

store.watch('firstName', function (name) {
    console.log('firstName changed:', name);
});

console.log(store.computed.fullName); // John Smith
store.state.firstName = 'Bob';
console.log(store.computed.fullName); // Bob Smith

console.log(store.state.amount); // 10
store.commit('increment');
console.log(store.state.amount); // 11

process.on('exit', () => {
    console.log(store.state.amount); // 12
});

store.dispatch('increment');

最近の仕事では Vue や Vuex を選択することが多く、今のところはある程度うまくいっていると感じているんですが、プロジェクトの数年先を考えて、Vue や Vuex が担っていることを因数分解して、他の何かで置き換えることができないか、といった思考実験や検証をちょいちょいおこなっています。 今回は(RxJS に依存してはいるんですが)Vuex を何か他のものに置き換えることはできるか、というところで上のようなコードを書いてみました。

かくかくしかじか を読んだ

読んだ。

東京タラレバ娘海月姫東村アキコの伝記的な漫画。高校時代から始まり、漫画家としてデビューするまでの絵の「先生」との交流を描く。

作者の中身を全て曝け出すような心理描写と「先生」が繰り返し発する「描け」というメッセージに心が揺さぶられた。

Vue.js のリストレンダリング( Virtual DOM 実装 )と相性の悪いデータの変更

この間、現場で直面したやつの個人的なメモです。対象のバージョンはこの記事を書いている時の最新バージョン v2.3.0。

以下のようなリストレンダリングをおこなうコンポーネントがあるとして、

<script>
export default {
  name: 'list',
  data() {
    return {
      items: [1, 2, 3, 4, 5]
    };
  }
}
</script>

<template>
  <ul v-for="item in items" :key="item">
    <li>{{ item }}</li>
  </ul>
</template>

items のサイズの上限を 5 として、新しいデータを追加したい場合は古いデータを削除することとする。
例えば、items [1, 2, 3, 4, 5] に 6 と 7 を追加する場合、items は [3, 4, 5, 6, 7] になるが、Vue.js の Virtual DOM 実装は以下のような diff/patch を適用する。

  • 新しい vdom の先頭の 3 が古い vdom にないか探し、見つかったので 3 のアイテムの DOM 要素をリストの先頭に移動させる(ここで key 属性の指定が効いている)。4, 5 も同様。
  • 6 は無いので、新しく DOM 要素を生成して追加する。7 も同様。
  • ここまでで 3, 4, 5, 6, 7, 1, 2 と要素が並んでいる。1 の要素は不要なので削除する。2 も同様。

以上で、新しいデータが Real DOM に反映された。このロジックは updateChildren 関数 (vue/patch.js at v2.3.0 · vuejs/vue · GitHub) に書かれている。上から下に読んでいくと分かりづらいので、上記のようなサンプルを用意し、DOM に Breakpoint を設定して実際にコードを動かしながら処理を追っていくと読みやすい。
本来であれば、1, 2 をそれぞれ変更して末尾に移動させるのが最も効率が良いはずだが、リストの最大サイズ + 変更のあった件数分 の DOM 操作が必要になる。リストの最大サイズが増えれば増えるほど、時間がかかる。

自分の場合は、最大サイズがそれなりに多かったのと、データの更新頻度が多く、Script の処理が増えて、fps がかなり低下してしまった(Timeline で調べたら、updateChildren 関数が Script 処理の 80% 以上を占めていた)。結局、この部分は直接 DOM 操作をおこなうことにした。Virtual DOM は、大体のケースである程度うまくいくが、このように極端なケースではうまくいかない。

ちなみに、フォームで何かを入力してボタンを押したらデータを1件追加する、あるいはリストから1件アイテムを削除するといったよくあるケースの場合は DOM 操作が 1 回で済むようになっている。