Subscribed unsubscribe Subscribe Subscribe

Islands in the byte stream

Technical notes by a software engineer

babel-nodeでたてるnode serverをguard-processで管理するときはkexec npm packageが必須

Babel CLIには babel-node というコマンドが付属していて、これはES2015+のスクリプトをそのまま実行できるという便利ツールです。

babel-nodeは大変便利なのですが、guardのpluginであるguard-processとは相性がわるく、babel-nodeで立ち上げたnode serverプロセスの再起動がうまくできません。これは nodeの process.spawn() まわりの問題だと思うのですが、詳細は調べていません。

ただし、babel-nodekexec がインストールされているとそちらを使います。そこで yarnpkg add -D kexec するだけでguardで管理できるようになります。これはドキュメントに乗っていないので、ここにメモしておきます。

該当コードはこのあたりです。

https://github.com/babel/babel/blob/master/packages/babel-cli/src/babel-node.js#L77-L94

Orma v4.0.0 - RxJava2 & SQLBrite-like query observables

GitHub - gfx/Android-Orma: A type-safe ORM for Android as a wrapper of SQLiteDatabase

v3.0.0からまだ一ヶ月くらいしかたってませんが、Ormaがv4.0.0となりました。

目玉機能は RxJava 2.x 対応で、これが後方互換性を維持したまま行うのが難しかったため、メジャーバージョンを上げることにしました。

RxJava 2.x 対応

機能的にはRxJava 1.x対応と同じ水準なはずです。

ただこれを機に、 Observable を返すものは AsObservable をサフィックスに、 SingleAsSingle に、 CompletableAsCompletableに…と、メソッド名を見ればRx observableのどの亜種を返すのかを分かるようにしました。

Query Observables

この機能は実験的です。いきなり消えることはないと思いますが、微妙にインターフェイスが変化したりする可能性があります。またこの機能の使い心地についてフィードバックを求めています!

SQLBriteのように、データセットの変更が流れるイベントストリームをRxJavaのobservableとして作成できます。このobservableを購読すると、Ormaによるデータの更新(INSERT, UPDATE, DELETE)を受け取れます。またその際Selectorを引数として受け取りますが、これはただのSelectorなので使っても使わなくてもかまいません。使い方の具体例としてはOrmaRecyclerViewAdapterなどがあります。

SQLBriteと違って完全にRx任せなので、デフォルトで同期的に変更通知が流れます。非同期で処理したい場合は、Rxのschedulersを使ってスレッドを調整してください。またこれもSQLBriteはsubscribe() した瞬間に最初のイベントがながれますが、Ormaは何も流れません。Ormaはあくまでも変更通知だけを流します。

READMEから抜粋:

// NOTE: Keep the observable instance. If it's released, the observable is disposed.

// create a query observable, which is a hot observable
Observable<Author_Selector> observable = db.relationOfAuthor()
        .createQueryObservable();

// subscribe the events
observable.flatMap(new Function<Author_Selector, Observable<Author>>() {
    @Override
    public Observable<Author> apply(Author_Selector selector) throws Exception {
        Log.d(TAG, "Author has been changed!");
        return selector.executeAsObservable();
    }
})
        .map(new Function<Author, String>() {
            @Override
            public String apply(Author author) throws Exception {
                return author.name;
            }
        })
        .subscribe(new Consumer<String>() {
            @Override
            public void accept(String name) throws Exception {
                Log.d(TAG, "name: " + name);
            }
        });

注意点としては、 createQueryObservable() の戻り値を強参照として保持しておく必要があります。そうでなければ、GCのタイミングで消えてしまいます。また、変更通知が伝播するのは同一のデータベースハンドル(OrmaDatabaseのインスタンス)の間だけです。つまり、プロセス間ではイベントが伝播しません。

また、実験的に createEventObservable() というメソッドも用意していて、こちらはイベントの種類(INSERT / UPDATEなど)もとれます。ただし、@Deprecatedマークをしてあるとおり、これは本当に実験的で、これについてどこからも意見がなければどこかのバージョンで消すと思います。

vscodeの AlanWalk/markdown-toc extensionで目次の自動生成すると捗るぞ

先日のNode学園祭で standard-readme という規格が紹介されていました。

これを眺めていたら、ミニマムでも目次(ToC)は必要ですよということでした。たしかにToCは読むほうからすると便利なんですよね。

一方でToCのメンテナンスは人間のやる仕事じゃないですよね。機械的に生成してメンテフリーにしたい。

ということで、vscodeのMarkdown ToCのいい感じの拡張を探したところ、AlanWalk/markdown-toc というのがよさそうだったので使ってみました。

marketplace.visualstudio.com

この拡張をインストールしてREADME.mdに以下のように書いておくと:

## Table of Contents

<!-- TOC depthFrom:2 -->

<!-- /TOC -->

以降、保存するたびにToCが自動的に生成されてこのタグの中に埋められます。これは保存するたびに最新の状態に更新され続けるので、画面分割してToCを眺めながらセクションの構成を考えたりできます。

私の直近のプロジェクトだと、OrmaのREADME.md が1000行近くあってメンテするのがつらかったのですが、これのおかげでだいぶ楽になりました。おそらく読む方も楽でしょう。

Flowtypeの`flow suggest`がすごい!

flow(1)のコマンドラインオプションを眺めていたら、 suggest というサブコマンドがありました*1

これは、JavaScriptファイルを引数にとって、そのスクリプトにflowtype annotationを付けるパッチを生成するコマンドのようです。

たとえばこういうスクリプトに対して:

function add(a, b) {
    return a + b;
}

console.log(add(1, 2)));

次のようなパッチを生成しました:

--- old
+++ new
@@ -1,4 +1,4 @@
-function add(a, b) {
+function add(a: number, b: number) : number{
     return a + b;
 }

これは結構すごいんじゃないでしょうか。

Reactの1ファイルにも試してみました。

flow suggest src/isomorphic/modern/class/ReactComponent.js で以下のような出力です。

--- old
+++ new
@@ -13,7 +13,7 @@
 
 var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue');
 
-var canDefineProperty = require('canDefineProperty');
+var canDefineProperty: boolean = require('canDefineProperty');
 var emptyObject = require('emptyObject');
 var invariant = require('invariant');
 var warning = require('warning');
@@ -21,7 +21,7 @@
 /**
  * Base class helpers for the updating state of a component.
  */
-function ReactComponent(props, context, updater) {
+function ReactComponent(props, context, updater) : void{
   this.props = props;
   this.context = context;
   this.refs = emptyObject;
@@ -57,7 +57,7 @@
  * @final
  * @protected
  */
-ReactComponent.prototype.setState = function(partialState, callback) {
+ReactComponent.prototype.setState = function(partialState, callback) : void{
   invariant(
     typeof partialState === 'object' ||
     typeof partialState === 'function' ||
@@ -85,7 +85,7 @@
  * @final
  * @protected
  */
-ReactComponent.prototype.forceUpdate = function(callback) {
+ReactComponent.prototype.forceUpdate = function(callback) : void{
   this.updater.enqueueForceUpdate(this);
   if (callback) {
     this.updater.enqueueCallback(this, callback, 'forceUpdate');
@@ -98,7 +98,7 @@
  * modern base class. Instead, we define a getter that warns if it's accessed.
  */
 if (__DEV__) {
-  var deprecatedAPIs = {
+  var deprecatedAPIs: {isMounted: [string, string], replaceState: [string, string]} = {
     isMounted: [
       'isMounted',
       'Instead, make sure to clean up subscriptions and pending requests in ' +
@@ -110,10 +110,10 @@
       'https://github.com/facebook/react/issues/3236).',
     ],
   };
-  var defineDeprecationWarning = function(methodName, info) {
+  var defineDeprecationWarning = function(methodName: string, info) : void{
     if (canDefineProperty) {
       Object.defineProperty(ReactComponent.prototype, methodName, {
-        get: function() {
+        get: function() : void{
           warning(
             false,
             '%s(...) is deprecated in plain JavaScript React classes. %s',
@@ -125,7 +125,7 @@
       });
     }
   };
-  for (var fnName in deprecatedAPIs) {
+  for (var fnName: string in deprecatedAPIs) {
     if (deprecatedAPIs.hasOwnProperty(fnName)) {
       defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);
     }

推論しきれてない部分もありますが、けっこうできているなという印象です。逆にいえば、これで補完されない部分は推論できないということなので、そういうところだけ人が書けばいいということですね。

*1:試したのはflow 0.35.0です

flowtypeのmagic typeは直接使うものじゃないよ

実践投入してわかったflowtypeのメリデメ // Speaker Deck で「Magic typeがドキュメントされてない(のに便利)」という説明がありましたが、それはmagic typeを直接使う想定ではないプライベートAPIからドキュメントに載せてないというだけだと思われます。

ドキュメントに書いていないということはそれは内部実装の詳細であって、将来にわたって使える保証はありません。

flowtypeのmagic typeについて - Qiita のためになんだかmagic typeという内部実装の詳細が広まってしまった感がありますが、このエントリでも述べているように、magic typeはそれに対応する構文があります。たとえば、$Either<A, B>A | B というunion typeを表現するための内部型です。他のmagic typeも対応する構文があるはずです。

magic type自体は面白いものだとは思いますし、内部でこういうものがあるということは知っておいていいかもしれませんが、コード上に現れるものではありません。flowtypeのドキュメントをよく読み、flowtypeの構文を使いましょう。

OrmaのRxJava2対応プラン

三行まとめ

  • Orma v3.x で RxJava2 対応をいれます。RxJava 1.xのメソッドはこれまでどおり使えて、後方互換製は保ちます
  • Orma v4.x で RxJava2 対応を主とします。RxJava 1.x対応は続けますが、"asObservable1" とリネームさされ、またそれらメソッドはdeprecatedとなります
  • Orma v5.x で RxJava2 対応のみとし、RxJava 1.x 関係のメソッドは削除します。

詳細

Migration to RxJava 2.0 · Issue #324 · gfx/Android-Orma · GitHub

ほかにも2.x用にブランチおよび成果物を分けるという方法があるのですが、Ormaは1人でメンテしているのでRxJava 1.x系ブランチとRxJava 2.x系用ブランチを分けるのはコスト的に現実的ではありません。よって、ブランチは分けずに、なるべく互換性を維持しつつメジャーバージョンアップのタイミングでRxJava2推しにしていく方法をとることにしました。

DroidKaigi 2017のトーク募集は11/1まで!

droidkaigi.github.io

私もひとつ「Android ORMの選び方」というトークを応募しました。ここ1年ほどOrmaの開発をしているので、その知見を活かしてなるべく公平なORMの比較検討をしたいと思っています。

他に個人的に興味があるのはこんな感じの話題です。誰か話してくれるといいな!

  • Web API clientの話
    • SwaggerとかJSON Schemaの実践の話を聞きたい
  • CIやテストの話
    • 個人的には得意分野ではあるものの、CIのパフォーマンスについてはまだ改善できそうな気がしている
    • Robolectric / JVM unit testingまわりの実践を知りたい
  • iOSとのアセットの共有の話
    • vector assetsが一般的になった今、どうやってアセットの共有をしているのかみたいな話
    • dpiごとにアセット用意するのはもうオワコンなのかな?とか
  • HTTP/2 x Androidの話
    • 画像を沢山動的にロードするケースでは有効だと思われるが…