第4回Kotlin勉強会 @ Sansanにいってきた
AltJava言語(特に手続き型のBetter Java言語)では3年前からCeylon推しだったんですが、Kotlinにはかなわなかったなーと負けを認めてKotlin勉強会に参加してきました。 ちなみに同時期、AltJS言語としてHaxeを推してたけどこれもその後の流れはTypeScriptの完全勝利っぽくて自分の目利きのできなさにウケるんですけど、CeylonとHaxeがこれまた雰囲気似てる言語で苦笑するしかないです。
Sansanさんのオフィスかっこいい
同じく渋谷宮益坂のビズリーチさんところも渋谷Javaで何度かお邪魔していますが、あそこもオフィスかっこいいし、渋谷すごいですね(小並
乾杯で勉強会スタートだ
メインゲストのたろうさんのご意向もあって🍺片手の勉強会となっていました。懇親会じゃなくて本体が乾杯で始める勉強会って初めて。Sansanさんごちそうさまでした。
KotlinでDSLを組む話
Sansanの山本さん(@boohbah)の発表。「KotlinでDSL」
DSLを作る話、ここでは自力でインタープリタを作る“外部DSL”ではなく、ホスト言語機能を応用してDSLを実現する“内部DSL”を作ろうという話です。Kotlinの豊富な糖衣構文機能は、こう言われてみるとまるでDSLを組むために用意されてきたのか、という気がしてきます。
使っている機能は
- 拡張関数 - 既存のクラスにメソッドを生やす。C#でいう拡張メソッド。
- invoke - この名前のメソッドを用意しておくと () 演算子で呼び出せる。C++でいう operator()。
- ()の省略 - 引数がラムダ式ひとつの場合は関数呼び出しの () を省略できる。
- レシーバ付き関数リテラル - ラムダ式の仲間なんだけど、ドット記法で呼び出すので拡張関数と同じくメソッドを生やしたような見た目になる。
- 中置記法 - 関数定義にinfixキーワードを付けると、
a.do(b)
をa do b
と書ける。拡張関数でも使える。要は、好きなクラスに演算子を生やせる。
これらを駆使した結果
buildState( "Start", { setAction( buildTransit( "TO_ZERO", "Zero"), { }) })
的なコードを
"Start" { "TO_ZERO" transitTo "Zero" action { } }
と見事にわかりやすいDSLに落とせましたねと。 なお、このためにString型に拡張関数を生やしていたことについてはツッコミが入っていました。
Android Studio(IntelliJ)のJava→Kotlin変換結果を人手で改善する話
Sansanの辰濱さん(Kenichi Tatsuhama, Sansan | SlideShare)の徳島からのリモート発表。「Java → Kotlin 変換 そのあとに。」
実戦で相当使い込んだんだとよくわかるTips集です。 ここでコメントするよりスライドを見てもらった方が早いですが、
apply
使って一時変数を追放するのは気付いていなかった人多そう。
そしてこの発表でも扱っているJavaの変数宣言のNullabilityを機械判定できない話が次の人の発表につながっていきます。
Javaとの相互運用で型がどうなるかって話
むろほしさん(@RyotoMurohoshi)の発表。「Mapped TypeとPlatform Typeの話」
ひとつめのトピックはプラットフォーム型。Kotlinでは型がnullを許容するかどうか明確だけどJavaではそうじゃない。nullを絶対に返さないメソッドもString
型のようにnullableな型で返すからKotlin側から扱いにくい。全部String?
型扱いにする? それも不要なnullチェックが必要になってノイジー。そこでString!
型。Kotlinの外から来たからnullableかnotNullかわかりませんよという型。それがプラットフォーム型。
これはややnull安全から一歩はみ出した言語仕様で、String!
型はString
にもString?
にも代入できてしまいます。特にString
に代入するときにはプログラマの注意でnullを渡さないようにする必要あり。実行時例外かっとびます。注意と。
もう一つが、int
, Integer
, List
なんかのJDK上の型がKotlinの型システム上でどう見えるか。型マッピングの仕様です。
KotlinではJavaのList
に当たるインターフェースが読み出しのみのList
とそれに書き込みを加えたMutableList
の2層に切り分けられていて、じゃあJavaのList
は相互運用でどう見えるか。Java側でアノテーションを付けることで区別させ、アノテーションなしだと(Mutable)List
というなんかすごい解釈がされるんだそうです。
Androidのビューコンポーネント組み立てをDSL化する話
k-kagurazakaさん(@kkagurazaka)の発表。「Ankoでコンポーネント指向」
XMLで画面を構築するつらみ
- XMLそのものがつらい
- アプリコードと行ったり来たりするのがつらい
- 型安全でないのがつらい
そこでJetBrainsが作っているDSLライブラリ、Ankoどうでしょうという話です。
linearLayout { checkBox = checkBox().lparams { gravity = Gravity.CENTER_VEDRTICAL } }
というコード、最初のDSLの話を聞いていると何が起こっているかわかりやすいですね。
Kotlin 1.1のbound call references機能の紹介がありました。この機能、Javaでは8ですでに導入されている機能の後追いになる、つまりインスタンスのメソッド参照のことなのですが、Kotlin 1.1のこの機能のちょっと恐ろしいところはJavaでgetter/setterの組として記述されたプロパティをひとつの参照対象とできるところ。執念を感じる。
ラムダ式禁止縛りに関数参照とカリー化で対抗しよう
トリはメインゲストたろうさん(@ngsw_taro)さんの発表。「ラムダ式禁止おじさん」
実際にはもちろん、ラムダ式禁止おじさんに張り合いたいのではなく、ラムダ式と同等の書き方をいろいろできるよという切り口からのKotlinの高階関数機能紹介です。
- 単なる一引数関数なら関数参照で単純に置き換え可能(
::println
) - 引数なしメソッドもメソッド参照で単純に置き換え可能(
String::toLowerCase
) - 二引数関数はカリー化すればいい。カリー化する仕組みは自作する。どうやって作るかというと関数に対してinvoke拡張関数を生やす(ラムダ式禁止おじさんを完全に殺す黒魔術)。(
(::minus)(5)
) - カリー化するにも、先頭でなく右側の引数を固定したい場合はどうする? プレースホルダ付きカリー化だ。そういうinvoke拡張関数を生やしてカリー化すればいい。プレースホルダはEnumで定義する。すると⋯ (
(::minus)(アレ, 3)
) - Java8のインスタンスメソッド参照(
"ABC"::toLowerCase
)みたいなのをKotlin 1.0でどうやるか(Kotlin 1.1ならできるようになるのは前の人の発表であった通り)。カリー化invokeが定義された状態で、こう。(String::toLowerCase)("ABC")
- nullableメソッドコール
?.
をするラムダ式はどうする?(引数型->戻り型)型を(引数型?->戻り型?)型に変換するような拡張関数を関数に生やしてしまえ。その拡張関数の名前をnullOk
とすると、(String::toLowerCase.nullOk()
)
そして結論はラムダ式使え。その理由はコードの読みやすさとかそういうのもあるものの、インライン展開というKotlinの利点が殺されてしまうからというのもあります。
懇親会
せっかくの懇親会、お二方としかおしゃべりしませんでしたチキンでごめんなさい⋯
お話しした方の話で印象に残ったのが、「JavaからKotlinへの移行は容易だがKotlinからJavaへは簡単に移行できないので、長期間続けるプロダクトに採用しにくい」という意見でした。そういえばAltJSではTypeScriptが猛烈な勢いで普及していますが、これの強みは言語そのものの良さだけでなく「JavaScriptにも容易に戻れる」という安心感はあるよなと思い起こしました。
全体的な所感
まだKotlinが普及初期であること、あとこの勉強会がまだ4回目と回数を重ねていないこともあり、マニアックな方面に偏ってしまわない誰でも理解できる内容の発表ばかりで、それでいて実のある話が聞けとても良いセッションでした。
LT持ち寄りの勉強会は全体としてのテーマというのが決められていないものなのですが、山本さんがDSL構築の話をしたあとにk-kagurazakaさんがその応用となる話をされていたのが見事にはまっていました。辰濱さんとむろほしさんの発表がnullabityでつながっていたのも一体感ありましたし、神の見えざる手を感じましたね。
発表者の皆様、Sansan様、ありがとうございました。