ライブラリのバージョニングのしかた

セマンティックバージョニングは守るとして、だいたいこんなポリシーでやってます。

  • 0.0.1 - proof of concept / minimum viable product
  • 0.1.0 - とりあえずリリースしてプロダクションに組み込んでみる
  • 1.0.0 - プロダクションに組み込んだ
  • 2.0.0 - セマンティックバージョニングに従うので、メジャーバージョンアップは機能ではなく単にAPI互換性を失うという印

あとは、alpha, beta, rcなどを接尾詞としてつけることもあります。

  • *-alpha - 開発中
  • *-beta - 安定してきた
  • *-rc - release candidate. プロダクションに組み込んでもOK

Suffix ArrayをRustで実装した

suffix arrayを一番簡単なアルゴリズムで実装する - アルゴリズム学習(その6) - $shibayu36->blog; を読んで、ちょうど自分も何らかの形で全文検索を一部実装してみようと思っていたのでRustで真似してみました。

Rustを選んだ理由は、以下の理由からです。

  • 実際に全文検索を実装するのに耐えうるパフォーマンスであること
  • パッケージマネージャなどのエコシステムが完備されていること

Rustについてはそれほど詳しくはないのですが、GCや例外がないとのこと。であればパフォーマンスチューニングがC言語並にやりやすい可能性がありますし、一度真面目に勉強してみたいと思っていました。Goと異なり、ジェネリクスがあるのも魅力的です。

というわけでコードこんな感じになりました:

pub fn make_suffix_array(s: &str) -> Vec<i64> {
  use std::collections::HashMap;

  let mut map = HashMap::new();

  for i in 0..s.len() {
    map.insert(&s[i..s.len()], i as i64);
  }

  use std::iter::FromIterator;

  let mut suffixes = Vec::from_iter(map.keys());
  suffixes.sort();

  return suffixes.iter().map(|suffix| *map.get(*suffix).unwrap()).collect();
}

#[test]
fn test_make_suffix_array() {
  let a = make_suffix_array("banana");
  assert_eq!(a, vec![5, 3, 1, 0, 4, 2]);
}

Rustに慣れていないので、この数十行を書くのも大変でしたがとりあえず動きます。エディタはIntelliJ Rustを使いました。あまり賢くはないもののコード補完があり、普段使っているRubyMineやAndroid Studioに近い感覚でコードを書けるのでわりと好印象です。

MBP2016が購入直後に故障したので返品してMBP2015を注文した

Macbook Pro 2016 13インチ タッチバー付きモデルを先月購入しましたが、3週間ほど経って充電できなくなる現象に見舞われました。Apple Storeのジーニアスバーに持ち込んでみてもらったところ、「ハードウェアの不具合と思われるが現在このモデルは部品がなく修理できない」とのことで、返品することにしました。代わりにMBP2015を注文し、これは来週届く見込みです。

故障をきっかけとして返品ということになりましたが、結果的には、むしろMBP2015に交換できてよかったと思っています。下記のとおり大きな不満点がありましたし、同様の故障が観測範囲内でも散見されるので、それならばMBP2015のほうが心配なく使えるという判断です。MBP2016を検討している方は参考にしてください。

MBP2016を3週間使ってみてどうだったか

  • ポートがUSB Type Cしかなくアダプタも貧弱で品薄状態。結局、購入から返品まで外部ディスプレイには繋げないままだった
    • 他にもアダプタを多数持ち歩かなければならず、非常に煩雑だった
    • もちろん過渡期ゆえのつらさという一面はあると思うが、自分はこういうことを楽しめないタイプなので純粋に苦痛だった
  • 充電ポートはMagsafe2のほうがよい
    • Magsafe2は充電状態を示すLEDがついており、一目で充電状態がわかるが、USB Type C充電コネクタは充電状態がわからない
    • Magsafe2は少しの力で外れるので足を引っ掛けてもコネクタが外れるだけだが、USB Type C充電コネクタは片手では抜けないくらいしっかりと刺さる
  • Touch Barは対応アプリが少なく、プラスでもマイナスでもなかった。対応アプリが増えてくればメリットを感じるかもしれない
    • ESCはやや遠く感じるが、慣れればそれほど問題はなかった
  • 新しいキーボードは悪くない
    • パタパタうるさいのは少し気になる
    • 力はそれほどいらないので慣れればむしろ楽で、旧MBA/MBPのキーボードが重く感じるレベル
    • Macbookは使ったことがないので差はわからない
  • Touch IDはよかった。特に1passwordを簡単にアンロックできるので使用頻度は高かった

スペック

MBP2016:

MBP2015:

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の話
    • 画像を沢山動的にロードするケースでは有効だと思われるが…