Edited at

AndroidをKotlinとRetrolamda+Lombokとで作る場合の比較

More than 3 years have passed since last update.

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


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リリースを待ちましょう(?