Kotlinアドベントカレンダーの4日目とかそんなの
昨日は com4dcさん の 自分を追い込んで頑張る方向で。WP用のKotlinのSytnaxHighlighter作ったという報告 で
明日は chibatchingさん の Kotlin + GAEな感じ です
はじめに
- なんでScalaと比較しないの?
- Kotlinは「Scalaで満足しているならいらない子」ってKotlinサイドが言っているので…比較するまでもないだろ?(とりあえず煽っとく方向でw)
- 俺がもう一年以上仕事でScala書けてないから…(現職程度にゆるい環境でScala書けるお仕事知りませんか?w 無いですね)
- なんでRetrolambda使用のAndroidでの比較なの?
- 仕事で書いている環境が(完全に自分の趣味で) そうだから
- 普通はこう書く!って感じの間違いは指摘でも編集リクエストでも構わないのでください(むしろお願いします)
比較内容
- ありがちななんかしらのCallbackをあれする
- 拡張関数的な奴
- Apkのサイズ
- stacktrace比較
あたり
環境
共通
- Android Studio 1.4.1で吐き出したテンプレベース
- 2.0? 知らない子ですね (2015/11/23現在)
- JDK 1.7.0_79
javaのような何かとretrolambda
- JDK 1.8.0_51
後はbuild.gradle見てください
ありがちななんかしらのCallbackをあれする
と言ってもOnClickListenerだけじゃ面白味が無いので、ここをだいたいそのまま使って今日の天気を表示する…ぐらいやることに…
あー、そういやこれLicenseとかあるのかしら?
起動部分は面倒なのでFloatingActionButtonタップ時にやるよ(ぇ
素のジャバっぽいなにか
この辺
上記をまんまコピったのを置いとく
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
WeatherService.currentTokyoWeather()
.subscribeOn(Schedulers.newThread())
.map(new Func1<WeatherResponse.Weather, Snackbar>() {
@Override
public Snackbar call(WeatherResponse.Weather weather) {
return Snackbar
.make(view, weather.getMain(), Snackbar.LENGTH_LONG)
.setAction("Action", null);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Snackbar>() {
@Override
public void call(Snackbar bar) {
bar.show();
}
});
}
});
サイコーですね
またレスポンスの型定義とかも…
ステップあたりでお金が貰える世界ならほんとサイコー
魔改造BeforeAfter(Retrolambdaとか使うよ)
fab.setOnClickListener(view -> WeatherService.currentTokyoWeather()
.subscribeOn(Schedulers.newThread())
.map(weather -> Snackbar
.make(view, weather.getMain(), Snackbar.LENGTH_LONG)
.setAction("Action", null))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(Snackbar::show);
特に新鮮味は無いですね
どちらかというと
- Response用に定義しているクラス
-
Observable#flatMapIterable 使っている部分
のほうがすっきり感出ている
前者はLombok、後者はretrolambdaのチカラ…
Unitテストは大体一緒になるので省いた
Kotlin版
plain(?)なコードをIDEのチカラで変換してチョットイジっただけ
interfaceを変換したらoperatorって出てきたんだけどなんだろう?
なお消した(おぃ
fab.setOnClickListener { view ->
WeatherService.currentTokyoWeather().
subscribeOn(Schedulers.newThread()).
map {
Snackbar.
make(view, it.main, Snackbar.LENGTH_LONG).
setAction("Action", null)
}.
observeOn(AndroidSchedulers.mainThread()).
subscribe { it.show() }
}
魔改造版と同様、
- Response用に定義しているクラス
-
Observable#flatMapIterable使っている部分
のほうがすっきり感出ている
mapの中の改行はIDE任せでフォーマットした結果
Kotlinのstyleガイドとかは有るかもしれないが調べてないので、そんなもんなんだろう…と勝手に納得している
lombokの@Value
使ったので、だいたい一緒だろうということで data class
にしといた
copyメソッドと同様のものを魔改造版で生やすなら@Wither
付ける必要あるけど…
個人的に良いと思った部分はここ
ヒアドキュメント(とKotlinの場合言うのか知らないけど)があるので、テスト用のレスポンスを可読性高いまま書ける点
完全に個人的な好みなのですが、(Unit)テスト用のDummyデータもコード中に置きたい派なので…
理由を書き出すと完全に別エントリになるのでやめます
この章?のまとめ
- lombokとか使わなくてもイミュータブルなクラスがさくっと作れるの楽
- だが別にこの程度では魔改造で「コードを書くだけなら」どうとでもなる
- 実はGroovy同様androidのbuild:gradle:1.3.1(正式名称なんだろ?)に対応してないんじゃないかとか思ってたけどそんなこと無くてよかった
- あくまで2015-11-17現在
- Groovyも 0.3.7-SNAPSHOTを自分でBuildすれば com.android.test plugin 以外 は動く
- ヒアドキュメントは便利(Kotlinに限った話ではない)
- 思いの外面倒になりそうだったNullableに、今回の例だとぶつからなくて何も確認できていない…
- KAnnotatorを使ってみるチャンスが…
拡張関数的な奴
書いている時にこんな資料を見つけた
よし比較しよう…
ViewからSnackBarを生成する拡張関数を用意
同一パッケージなのでImportも要らないしで、差分の通りすっきり…
LombokにもExtensionMethodあるよ!
こんなコードをかけば…
と思ったのですが…これlambda式内だと使えないっぽいですね…
(何気に使ったことなかった)
retrolambda先生によってlambda式が$Lambda.classみたいな感じに生成されるはず
で、別にこのクラスにアノテーションは付与されていないよね?
というオチっぽいです(´・ω・`)
(ちゃんとは調べていないのであれですが…)
なおエラーはこんな感じ
/kotlin-adventcalender-2015/retrolambda/src/main/java/com/github/daneko/android/retrolambda/MainActivity.java:32: error: cannot find symbol
.map(weather -> view.snack(weather.getMain()))
^
まとめ
拡張関数的な奴は自分の確認した範囲ではKotlinの勝ち
ぐぬぬ…
Apkサイズ
とりあえずリリースビルドで比較しましょう
Kotlinのpruguard設定は公式ドキュメントでは特に言及されていないので何も設定していない
(kotlin側が上手にやっているのだろう)
retrolambdaとかlombokとか使っているとlintで
java.lang.UnsupportedOperationException: Unknown ASTNode child: LambdaExpression
とか出てきて、なんだかコンソール見るのが辛くなるけどそこは精神力でカバーしましょう
こういうのあるけど使ったことはない
find -E ./ -regex ".*(debug|release)\.apk" | xargs ls -la -h
-rw-r--r-- 1 daneko 46682944 2.2M Nov 24 14:37 .//kotlin/build/outputs/apk/kotlin-debug.apk
-rw-r--r-- 1 daneko 46682944 1.0M Nov 24 14:38 .//kotlin/build/outputs/apk/kotlin-release.apk
-rw-r--r-- 1 daneko 46682944 1.8M Nov 24 14:38 .//plain/build/outputs/apk/plain-debug.apk
-rw-r--r-- 1 daneko 46682944 1.0M Nov 24 14:39 .//plain/build/outputs/apk/plain-release.apk
-rw-r--r-- 1 daneko 46682944 1.8M Nov 24 14:39 .//retrolambda/build/outputs/apk/retrolambda-debug.apk
-rw-r--r-- 1 daneko 46682944 1.0M Nov 24 14:40 .//retrolambda/build/outputs/apk/retrolambda-release.apk
うん、誤差
もちろんアプリとしてKotlinの機能を使っていけば、strip出来るものがへるでしょうから、
サイズはDebug側に寄っていくはず…
とは言うものの、debugビルドでの差分を見ても0.4MB
なわけで…
これを気にすることは今だと無さそう?
気にする側に倒すと、retrolambda側は優秀ですねw
ちなみにmethod数でkotlinの部分だけ抜き出すとこんな感じ
kotlin: 7786
annotation: 12
concurrent: 31
internal: 9
io: 332
jvm: 600
functions: 23
internal: 563
unsafe: 2
math: 22
properties: 58
reflect: 58
support: 15
test: 109
text: 117
util: 2
なお、今回の構成だとほとんどStripされて存在すら無いレベルだった…
まとめ
もっと大きなアプリで比較したいけど… 多分大抵の人は気にしなくて良い気がする
StackTrace
なにかあったらそこから追いかけるしか無い!
ということで理由はないけど、Snackbar表示前にRuntimeException!
あくまでTrace追ってみたいだけなので、そもそも色々とオカシイ!ってのは無視してください
素
なんていうかその…どうしてこうなった
ステップ数でおカネが(ry
final RuntimeException cause = new RuntimeException("cause");
WeatherService.currentTokyoWeather()
.subscribeOn(Schedulers.newThread())
.map(new Func1<WeatherResponse.Weather, Snackbar>() {
@Override
public Snackbar call(WeatherResponse.Weather weather) {
return Snackbar
.make(view, weather.getMain(), Snackbar.LENGTH_LONG)
.setAction("Action", null);
}
})
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<Snackbar, Observable<Snackbar>>() {
@Override
public Observable<Snackbar> call(Snackbar snackbar) {
return Observable.error(new RuntimeException("test", cause));
}
})
.subscribe(new Action1<Snackbar>() {
@Override
public void call(Snackbar bar) {
bar.show();
}
},
new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Log.w("test", throwable);
}
});
表示した結果
java.lang.RuntimeException: test
at com.github.daneko.android.plain.MainActivity$1$3.call(MainActivity.java:48)
at com.github.daneko.android.plain.MainActivity$1$3.call(MainActivity.java:45)
at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:55)
at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.pollQueue(OperatorObserveOn.java:208)
at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber$2.call(OperatorObserveOn.java:170)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:155)
at android.app.ActivityThread.main(ActivityThread.java:5511)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.RuntimeException: cause
at com.github.daneko.android.plain.MainActivity$1.onClick(MainActivity.java:33)
at android.view.View.performClick(View.java:4102)
at android.view.View$PerformClick.run(View.java:17085)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:155)
at android.app.ActivityThread.main(ActivityThread.java:5511)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)
at dalvik.system.NativeStart.main(Native Method)
まあ追えそうですね
CauseByの一つ目でonClick指してますし
proguardかけた状態
java.lang.RuntimeException: test
at com.github.daneko.android.plain.d.call(MainActivity.java:1048)
at b.d.a.j.a(OperatorMap.java:55)
at b.d.a.w.call(OperatorObserveOn.java:1208)
at b.d.c.g.run(ScheduledAction.java:55)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.RuntimeException: cause
at com.github.daneko.android.plain.a.onClick(MainActivity.java:33)
at android.view.View.performClick(View.java:5198)
at android.view.View$PerformClick.run(View.java:21147)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
現状の設定なら余裕な感じ
retrolambdaとか
final RuntimeException cause = new RuntimeException("cause");
WeatherService.currentTokyoWeather()
.subscribeOn(Schedulers.newThread())
.map(weather -> Snackbar
.make(view, weather.getMain(), Snackbar.LENGTH_LONG)
.setAction("Action", null))
.observeOn(AndroidSchedulers.mainThread())
.<Snackbar>flatMap(bar -> Observable.error(new RuntimeException("test", cause)))
.subscribe(Snackbar::show, th -> {
Log.w("test", th);
});
java.lang.RuntimeException: test
at com.github.daneko.android.retrolambda.MainActivity.lambda$null$1(MainActivity.java:35)
at com.github.daneko.android.retrolambda.MainActivity.access$lambda$2(MainActivity.java)
at com.github.daneko.android.retrolambda.MainActivity$$Lambda$5.call(Unknown Source)
at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:55)
at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.pollQueue(OperatorObserveOn.java:208)
at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber$2.call(OperatorObserveOn.java:170)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:155)
at android.app.ActivityThread.main(ActivityThread.java:5511)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.RuntimeException: cause
at com.github.daneko.android.retrolambda.MainActivity.lambda$onCreate$3(MainActivity.java:28)
at com.github.daneko.android.retrolambda.MainActivity.access$lambda$0(MainActivity.java)
at com.github.daneko.android.retrolambda.MainActivity$$Lambda$1.onClick(Unknown Source)
at android.view.View.performClick(View.java:4102)
at android.view.View$PerformClick.run(View.java:17085)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:155)
at android.app.ActivityThread.main(ActivityThread.java:5511)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)
at dalvik.system.NativeStart.main(Native Method)
lambda…の文字が…
CauseByの3つ目でonClickが出てきます
コード生成というかバイナリ弄る系の宿命ですね…
java.lang.RuntimeException: test
at com.github.daneko.android.retrolambda.MainActivity.a(MainActivity.java:14035)
at com.github.daneko.android.retrolambda.c.call(Unknown Source)
at b.d.a.j.a(OperatorMap.java:55)
at b.d.a.w.call(OperatorObserveOn.java:1208)
at b.d.c.g.run(ScheduledAction.java:55)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.RuntimeException: cause
at com.github.daneko.android.retrolambda.MainActivity.a(MainActivity.java:8028)
at com.github.daneko.android.retrolambda.a.onClick(Unknown Source)
at android.view.View.performClick(View.java:5198)
at android.view.View$PerformClick.run(View.java:21147)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
proguardの結果、逆にあっさりしてて読みやすそうな気がしてしまう…w
本来の該当箇所である部分のonClickの部分で Unknown Source
になっているのは、やっぱり追いかけるの大変になるだろうなぁ…
(実際生成されたクラスだろうからコードは無いはずだしw)
kotlin
val cause = RuntimeException("cause")
WeatherService.currentTokyoWeather().
subscribeOn(Schedulers.newThread()).
map { view.snack(it.main) }.
observeOn(AndroidSchedulers.mainThread()).
flatMap { snack -> Observable.error<Snackbar>(RuntimeException("test", cause)) }.
subscribe { it.show() }
retrolambda版と比較してflatMap
部分での型を明示している場所が異なるんだけど…
ここで明示しないと怒られた…
というだけで深い意味はない
retrolambda版はどっちでもイケる
どう推論が効くか…なんだろうけど、所詮ニワカプログラマーの俺にとってはそんなもんなんだろう…で納得
(だれか詳しい人が丁寧に説明していくれないかなぁ|д゚)チラッ)
java.lang.RuntimeException: test
at com.github.daneko.android.kotlin.MainActivity$onCreate$1$2.call(MainActivity.kt:31)
at com.github.daneko.android.kotlin.MainActivity$onCreate$1$2.call(MainActivity.kt:16)
at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:55)
at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.pollQueue(OperatorObserveOn.java:208)
at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber$2.call(OperatorObserveOn.java:170)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:155)
at android.app.ActivityThread.main(ActivityThread.java:5511)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.RuntimeException: cause
at com.github.daneko.android.kotlin.MainActivity$onCreate$1.onClick(MainActivity.kt:26)
at android.view.View.performClick(View.java:4102)
at android.view.View$PerformClick.run(View.java:17085)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:155)
at android.app.ActivityThread.main(ActivityThread.java:5511)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)
at dalvik.system.NativeStart.main(Native Method)
お、素のやつと同じような感じ…
java.lang.RuntimeException: test
at com.github.daneko.android.kotlin.c.call(MainActivity.kt:1031)
at c.d.a.j.a(OperatorMap.java:55)
at c.d.a.w.call(OperatorObserveOn.java:1208)
at c.d.c.g.run(ScheduledAction.java:55)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.RuntimeException: cause
at com.github.daneko.android.kotlin.a.onClick(MainActivity.kt:26)
at android.view.View.performClick(View.java:5198)
at android.view.View$PerformClick.run(View.java:21147)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
良い
まとめ
Kotlin優秀じゃねーか…
感想
2年前に試してみようとした時はWeb上のコンソールで動く奴がコンパイルは通らねえし、なんか色々と面倒だった記憶があります
今回触ってみてそれなりに使えるな(何故上から目線…)と思いました
本当は今回くらい簡単なやつで「ザッケンナコラー」みたいな状態になることを望んでいたのですが…
今後何かのやつでは採用したいなぁ…と思ったり思わなかったりしました
ただ、Androidの開発ツール周りは イラつくレベルで更新される ので、
- 新しい開発機能がついた!
- が、Kotlin(のplugin)で動くとは言っていない
というパターンは今後発生すると思います(まあKotlinに限った話ではないけど)
しかもこれ書いている時(2015/11/23)とかに2.0の話が出るし…ってことは…
なのでまあ「Productに反映させる」のならKotlin1.0が正式にでたあと、
- Androidの開発ツール更新時にどういう追従方法を取るのか
を一度確認してから…のほうが無難かも…
(この辺はGroovy/Robospockなどで過去に痛い目を見ているので…)
とは言うものの、この手のことは言い出すと「素」以外使えなくなるので…
もちろんProductだからこそ、なにか特別な理由がない限り「最新版を即座に反映させる必要はない」はずですが…
ほら…試したいじゃん…ねぇ…
関連として
- Jack and Jillでjava8サポートが来た場合にどうなるか
あたりも気になるところ…
それとやはり Scalaと比べて本当に学習コスト/入門の敷居は低いか? という点は個人的には払拭出来ていません
(そもそもScalaと比べて簡単だよーみたいなのがセールスポイントの一つだったような?)
まあその辺はエバンジェリストであるタロウ先生の2012年後半から更新されていない日本語解説サイトが1.0に合わせて更新されることで、敷居がぐっと下がるのでは…と期待しています
今回のサンプルはエラーハンドリングとか基本何もしてないので、使う際は自己責任でどうぞ
手を付けたかったけど付けなかった
Delegates
名前的にlomokのDelegateと比較しても面白そうだったけど、意味合い的に違うっぽいのでやめた
KAnnotator
個人的な好みでNullSafe機構のあれがウザいと思っている人間です
なので、FunctionalJava + KAnnotatorでどれくらいうざく なくなる だろうか…
とかやりたかったけど、今回のテキトーコードだとそもそも出番が無かった…
reference
-
今回のコード
- エラーハンドリングも何もないので、流用するなら自己責任で
- Kotlinのreference
- OpenWeatherMap
- retrofit
- [Android] OkHTTP + Retrofit + RxAndroid で REST クライアントを実装する
- Retrofit2のRxJavaCallアダプタを試す
- JetBrains/kannotator
- KAnnotatorでJavaライブラリでもNull-Safety機能を使う #Kotlin
- 10 Extension Functions of Kotlin in Android Development
- Kotlin言語の日本語解説サイト
2015/12/03 追記
上記から、「なんだ使えるじゃーん」と思って実際手元で動作確認するAndroidアプリに使って一瞬ハマったところのメモ
拡張関数で中途半端にシグネチャが被ると辛い?
kotlinはいろんな便利メソッドを拡張関数として提供してくれているようです。
で、この中にこれがあります。
なんてことはない、Iterable#map
を作ってくれているだけですね。
さて、ここでFunctionalJavaのOptionを見てみましょう
Iterableを実装しています
でOption#mapを見ます
引数に取るFはこんなのです
なのでSAM変換により
Option.fromNull(value).map{ v -> ... }.orSome(default)
と書けそうな気がします…
が、コンパイルは通りません
この辺だけを読むとイケそうですけど、ダメ
完全に推測 ですが、下記の理由だと思います
- 拡張関数で
fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R>
がOptionにも生える -
optionValue.map{a -> b}
的な呼び出しをする - Optionには上記の
#map(Function1)
と既に実装されている#map(F)
が存在する - Function Literalが引数として渡され、
#map(Function1)
が拡張関数により与えられているため、F
へのSAM変換はしない(拡張関数によって与えられたほうが型としてあってしまうし) - 結果Option型でなくkotlinのList型が返る
じゃあどうするか
Option.fromNull(value).map(F<A,B>{ v -> ... }).orSome(default)
これならコンパイラは間違えないですね…
なんだろう…この惜しい感じ…
2015/12/28 追記
1.0.0-beta-3595 -> 1.0.0-beta-4584 に上げたら直り(?)ました
ただReleaseNoteみてもどれだか分からん…
Calling Kotlin from Java
ここのドキュメント読んだだけだと、Function Literalで定義されたメソッドをJavaから呼び出す方法が載ってないんだけど…
常識なんでしょうか?
Function Literal は この辺に変換されるっぽいので、
class Hoge {
companion object {
// これのfun版が分からん…
val sum: (Int, Int) -> Int = { x, y -> x + y }
fun run(f : () -> Unit){f()}
}
みたいなのがあったら
Hoge.Companion.getSum().invoke(1,2);
Hoge.Companion.run(new Function0<Unit>() {
@Override
public Unit invoke() {
return Unit.INSTANCE;
}
});
みたいな感じで呼び出せる
なんだけど、どこかにLiteralはFunction…になるって書いてあるんだろうか…
まあIDE経由で見れば問題は無いですね
あとJava側から呼び出す前提なら@JvmStatic
つけたほうが親切ですね
Function Literal は カリー化された奴はない?
日本語がオカシイ気がしますがつまりはこういうことです
Functional Java の Ord#ord を見てみましょう
これをretrolambda(ていうかjava8のラムダ式)で使うには
Ord.<A>ord(a -> b -> /*なんかOrderingを返すようなあれ */)
になります
これをIDEのチカラでKotlinに変換するとこんな感じ
Ord.ord<A> { a -> { b -> /*なんかOrderingを返すようなあれ */ } }
別に行けそうなんですがだめ…
じゃあどうするか
Functional Java の FNFunctionsにcurryメソッドがあるのでこんな感じならとりあえず…
Ord.ord<A>(F2Functions.curry{ a:A, b:A -> /*なんかOrderingを返すようなあれ */ } )
結局コンパイルが通って(そらそうだ的な形ですが…)、まあ比較的わかりやすくて短いかなぁ…
というのがこの形という…
kotlinに curried 的なメソッドあった気がするけど、どこに現行のDocumentがあるのかわからなかった。
これ結構困る(めんどい)んで、だれか解決方法ください。
2016/01/04 追記
より短く書くという意味では
val ord:Ord<A> = Ord.ord{ a -> fj.F { b -> /*なんかOrderingを返すようなあれ */ } }
みたいな感じのほうが短いかな?
この辺やはり型推論の順序とかが関わっているんだろうなぁと勝手に思っていますけど…
どうなんでしょうね?
ということで1.0リリースを待ちましょう(?