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まで!
私もひとつ「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の話
- 画像を沢山動的にロードするケースでは有効だと思われるが…
AndroidのCIはlintOptions.textReportを有効にしておくと捗る
ThreeTenABPの差分(v1.0.3...v1.0.4)を見ていて気づいたんですが、Android Lintの結果をテキスト形式で標準出力に出すことができるんですね。マニュアル にも書いてありますが、今日初めて知りました。
lintOptions { textReport true textOutput 'stdout' }
これをするとコマンドラインのビルドでlintのwarningやerrorが表示されるので、たとえばCIがlint errorで落ちたときに手元でlintを走らせることなく原因がわかります。
日頃有効にしておくとwarningでも対処しようというプレッシャーになるので、やって損はなさそうです。
Orma v3.0.0をリリースしました
Orma v3.0.0をリリースしました*1。
実にv2.6.0から100コミット以上、v2.0.0から8ヶ月後のリリースとなります。
このバージョンは、内部的には大きく変えたものの、インターフェイス的にはそれほどv2.6.0と変わりありません。v2.6.0で警告なしに動いていたものはそのまま動くと思います。
transctionAsync()
まわりで古いメソッドを消したので、修正が必要なこともあるかもしれません。たとえば、DroidKaigi2016では次のような修正が必要でした。
Upgrade Orma to v3.0.0-rc2 by gfx · Pull Request #419 · konifar/droidkaigi2016 · GitHub
ひとつ注意点として、Android Gradle Plugin (正確のはその一部であるDataBinding library)との相性問題があります。CHANGELOGにも書きましたが、以下のような組み合わせでないと動きません。
- Android Gradle Plugin 2.1以下 + Orma v2.x
- Android Gradle Plugin 2.2以上 + Orma v3.x
これは、Android Gradle PluginとOrmaがfull qualified nameが全く同じクラスをもち実装が微妙に異なるANTLR (TunnelVisionLabs ANTLR vs Original ANTLR) に依存しているためです。
android bug ticket: Issue 200925 - android - Databinding plugin uses non-standard Antlr dependency. - Android Open Source Project - Issue Tracker - Google Project Hosting
このため、Android Gradle PluginとOrmaを同時にアップデートする必要があります。お手数をおかけしますがよろしくおねがいします。
*1:Orma入門もv3.0.0に対応済みです: Android Orma入門 - Islands in the byte stream
Orma v3.0.0-rc2 と内部設計の見直し
Orma v3.0.0-rc2をリリースしました。CHANGESはこちら:
Android-Orma/CHANGELOG.md at master · gfx/Android-Orma · GitHub
三行まとめ
- あるモデルが複数の同じ型のモデルをdirect associationで持てるようになりました
- 内部的に非常に大きな変更を行ったので新しいバージョンを検証してみてください
- v3.0.0はもうしばらく寝かせてから来週あたりにリリース予定です
解説
このバージョンは、3.0.0-rc1と比較してただ1つのバグフィクスを含みます。しかし、この修正のために内部の設計の見直しが必要だったため、変更量が非常に大きいものになっています。
そもそもdirect associationとはなにか、このリリースで解決したかったことはなにかを説明します。
Direct Associations
OrmaモデルAが別のOrmaモデルBを直接カラムとして保持することを、Ormaでは "direct association" と言っています。
たとえば以下のようなモデルがあるとき、BlogEntryはauthor:User
のdirect associationを持つということです。
@Table public class User { @PrimaryKey long id; @Column public String name; } public class BlogEntry { @PrimaryKey public long id; @Column User author; @Column String title; // ... }
これをSELECTでデータを検索してモデルを構築する際は、JOINを使って関連するモデルのデータもすべてロードする、いわゆるeager loadを行います。Ormaはモデルに制約を課していないので、direct associationの際はeager loadする他ないのです ((lazy loadを行うにはSingleAssociation<T>
を使ったindirect associationにすればできます。しかし、モデルのインターフェイスが変わってしまいます。)) 。まあ、eager loadはそれが起きると分かっていれば、N+1クエリも発生しないしクエリの結果も予想しやすいので便利なものではあります。
JOINs
さてOrma v2の時点では、BlogEntryを生成するためクエリは次のようなものでした。
SELECT BlogEntry.id, BlogEntry.author, BlogEntry.title, User.id, User.name FROM BlogEntry LEFT OUTER JOIN User ON BlogEntry.author = User.id -- ...
fully-qualified nameで参照しているので、仮にBlogEntryとUserに共通するフィールド(たとえばid
)があったとしても問題なくクエリを発行できます。
ところが、このやり方だとBlogEntryがさらに別のUserをdirect associationで保持したとたんうまく行かなくなります。たとえば、BlogEntryに User u2
というカラムに追加したとすると、SELECTの再は次のようなクエリが生成されます。 SELECT句のUser.id /* for author */
と User.id /* for u2 */
を区別する方法がないため、SQLiteに渡すとエラーになります。
SELECT BlogEntry.id, BlogEntry.author, BlogEntry.title, User.id /* for author */, User.name, User.id /* for u2 */, User.name FROM BlogEntry LEFT OUTER JOIN User ON BlogEntry.author = User.id LEFT OUTER JOIN User ON BlogEntry.u2 = User.id -- ...
Table Name Aliases
この問題を解決するには、それぞれのテーブルに対して関連するテーブル群においてユニークな別名(alias)を付ける必要があります。またそのことにより、カラム名を静的に生成できなくなります。たとえばUserがどのモデルから参照されているかによって、別名が変わる可能性があるからです。
この「関連するテーブル群においてユニークな別名を付ける」というのが Aliases というクラスで、たとえば "BlogEntry.author:User" のようなカラムに至るパスを与えると "u10" (User型のカラムなのでu
)といった別名を割り当てます。これは同じパスに対しては常に同じ値を返すので、これを信頼してJOIN句やWHERE句のカラム名を構築していくことになります。
The Schema Classes (v3)
というわけでOrma v3 (rc2) におけるスキーマクラスはこんな感じになりました。
v2での同じクラスは次のようになります。
クラス変数として定義しているものがほとんどなくなり、多くがインスタンス変数になっているのが特徴です。
特に、カラム定義(ColumnDef
)がstatic fieldではなくinstance fieldになったので、これらを使っているアプリケーションは変更が必要です。
また getSelectFromTableClause()
が単一の文字列ではなくなったので、少し見通しも悪くなりました。しかしこれはやむを得ない変更です。
全体として、動的に構築するSQLが増えた分パフォーマンスが低下している可能性はあります。とはいえ内蔵のベンチマークだとほとんど差が見られないので、許容範囲だと思います。
さて以上のように、v3は内部的に大幅に変更を加えました。既存のテストはすべてパスしているので大丈夫だとは思いますが、不安定になっている可能性もあるので検証してみてください。v3.0.0正規版も10月半ばから下旬くらいにはリリースする予定です。
npm scripts 内では zshの**が使えないのでmochaの場合はそのまま渡すこと
npmのpackage.jsonに以下のように書いていたら、明らかに実行されるファイルが少なくてどうしたものかと思っていました。
"scripts": { "test": "mocha --opts spec/client/support/default.opts spec/client/**/*.spec.js" }
理由は詳細には調べてませんが、おそらくこのスクリプトがzshではなくbashで実行されるためでしょう。
npm/npm#10481 によると、mochaは "**" をzsh的な意味で展開するのでそのまま渡せばいいということでした。
はたして以下のように書き換えるとちゃんと動きました。
"scripts": { "test": "mocha --opts spec/client/support/default.opts 'spec/client/**/*.spec.js'" }
Orma v3.0.0-rc1 for Android Studio v2.2 / Android Gradle Plugin v2.2
Orma v2.x は Android Gradle Plugin v2.2 と同時に使えないという問題がありまして、このたびこれを修正した v3.0.0-rc1 を出しました。RCとはいうものの、中身はdeprecated methodsを消したくらいであまり変えていないので、安定性は v2.6.0 と大差ないはずです。
Orma v3.x は逆に Android Gradle Plugin v2.2 より古いものとは同時に使えないのでご注意ください。
また、Orma v3.0.0-rc1 は RxJava v1.2.0 の、晴れて @Beta
となった Completable
を使っています。したがって、RxJavaのアップグレードも必要です。
v3.0.0 正規版は #189 を修正したら出すつもりでいます。これがわりと内部的に大規模な変更が必要なので、RCを更にいくつかリリースすることになるかもしれません。
#write_code_every_day の試みから1年たってどうだったか
そういえば write_code_every_day 活動をやろうと思い立ったのがちょうど1年くらい前だったなと思いだしたので振り返りです。
この話をしたのは Rebuild: 120: Swiftonomics (gfx) だったかな?
で、GitHubの草の現状です(ログアウトしてpublic contributionsのみ計測):
はい、5月くらいまではそれなりに継続して、そのあとしばらく空白の多い日々で、最近また少し復活しているという感じでしょうか。
もともとこの活動は日々のアウトプットを増やすのが目的で、GitHubの草は所詮モチベーションを上げるための小道具としての位置づけです。なので、草を継続するためのハック、たとえば日付変更の前後にわけてコミットして草を稼ぐなどはしないと決めていました。こだわりすぎてストレスを感じたり家族との関係が荒れたりするのは本末転倒ですからね。
実際この活動のおかげでOrmaの開発やブラッシュアップがかなり進んだので、やってよかったなと思いますし、これからも継続的にやっていこうという思いを新たにしました。逆に、デメリットは特に感じませんでした。わりとゆるくやっていたからかもしれませんが、このくらいでいいのかなと思っています。
そういえば、GitHubのアップデートで過去の草も見れるようになったので見てみたのですが、学生最後の年の2010年のコントリビューション数が4000超えていて驚きました。学生時代はそれなりに忙しかったと思っていましたが、やはり社会人とくらべると余暇はあったのだなあと。