イベントレポート:【勉強会】Google公式サポート!Kotlin言語入門 / Kotlinのスコープ関数を考える

どうもayijkです。

前回までは仙台のラーメンブログ観光ブログでしたが,今回はもう少し技術寄りの話。久しぶりですね。

今回は6/13(火)に標記の勉強会に参加してきたのでそのレポートをお送りします。
恐れながら勉強会で理解が及ばなかったところがあったので,その補足もちょっと書いてみました。プログラミング言語”Kotlin”に興味のある方,読んでいただければ幸いです。

イベント概要

(画像は下記,イベント詳細ページからお借りしました)

今回も主催はサポーターズさんです。最近はよくサポーターズ勉強会・講演会に参加しています。いつも無料なので本当にありがたいです。
テーマは今アツいプログラミング言語・Kotlinについて。

“Kotlin言語入門”とある勉強会ですが,私は何だかんだでKotlinを書き始めて1年半くらいです。なのでさすがに初心者ではないのですが,大学1年生以来仲良くして貰っている友人Tと示し合わせ,また社外のエンジニアの交流の輪を広めるために参加してみました。

Kotlinとは

上で「今アツいプログラミング言語」と紹介しましたが,きっとご存じでない方もいると思うので簡単にKotlinについて説明してみます。

星の数ほどあるプログラミング言語の中でも,多くのシステムで使われているのが”Java(ジャヴァ or ジャバ)”という言語です。文法に縛りが多く,「堅苦しい言語」として忌避されることもありますが,その質実剛健な特性を持ちながらC/C++言語ほどメモリ管理に気を配らなくてもよい気軽さを兼ね備えていることもあり,愛用者は多いです。私も開発しているAndroidアプリも,Java言語で開発するのがスタンダードです。
ただ,やはりその歴史の深さに反比例するように,俗に言う「モダン」なプログラミング言語に対して「空気を読む」ことについては水をあけられている印象は否めません。宣言は冗長だし,functionalな書き方はできないし(Java7においては),REPL[note]Read-eval-print loopの略で対話型評価環境のこと。[/note]もありません。

そんなわけで,ぶっちゃけ皆Javaに対して文句を言いまくってますが,それも愛ゆえです(だって愛の反対は無関心って言うじゃないですか)。

また,その愛ゆえに「ポストJava」なる言語が次々と登場しています。Javaは”Java Virtual Machine(JVM)”という仮想マシン上でバイトコードを展開してネイティブコードを生成するのでクロスプラットフォーム開発に向いているという特徴があります。一部の例外はありますが,とあるコードをコンパイルしたバイナリを,WindowsでもMacでもLinuxでも同様に実行することができるのです。これってすごい便利なことです。
話が少し逸れましたが,ポストJavaなる言語はそのJVMの上に乗っかる形でバイナリを生成・実行します。早い話が「頭がよくてJavaの実行環境さえあれば動く」という取り回しの良さが人気を博しています。
爾来,そんなわけでポストJava言語は隆盛を極めています。細かい向き不向きはありますが,有名なところだけでも,

  • Groovy
  • Scala

などがあります。
最近は,Scala(Scalable Languageの略)が流行りの機械学習などに向いていることもあり人気急上昇中です。ところが、ScalaがポストJavaとしての地位を固めようとしてきた昨今,きら星の如く登場したのが Kotlin(ことりん) という言語です(発表は2011年とScalaの2003年よりも大分後発です)。
プログラミング言語を利用した開発には通常IDE=Integrated Development Environment=統合開発環境というソフトを使うことが多いのですが(特にJava系の型付き言語はこれがないとやってられません),現在のJava開発のデファクトスタンダードはJetBrains社製のIntelliJ IDEAというソフトです。
で,KotlinはそのJetBrains社が開発したJava系言語なんですね。Javaの鬼とも言える企業が作っただけあって,IDE連携も抜群ですし,何よりも最後発なだけあって他の言語の成功事例をふんだんに取り入れており、書いていて気持ちがよいのです。

日本でも2年くらい前から主にAndroid開発者の中でKotlinが話題になり始め,2016年2月にバージョン1.0をリリースすると,ことAndroid開発におけるKotlinの位置づけは日に日に大きくなっていきました。
Javaよりも軽量で,IDEの補完に優れ,functionalな書き方もできるし,冗長な宣言も必要としない。Kotlinは段々と,そのユーザを増やしていき実際のプロダクトにも乗せられるようになっていきました。

一方私はちょうど1年前くらいから,Android開発においてKotlinを使うようになりました。去年に公開したメモアプリ,AnyReminderもフルKotlinで書いています。

そんな中、Googleが先日のGoogle I/Oで、Android開発におけるKotlinのサポートを明言するという大事件が起きました。
これからは、Android開発の第一線言語は恐らくKotlinになっていくのでしょう。iOSにおけるSwiftと同様、プログラミング言語の置き換えも段々と進んでいきます。

そんなわけで今もっともアツい言語、Kotlinの勉強会、開催です!

スコープ関数を考える

講師は藤田 琢磨さんです(Twitter: @magie_pooh)。
すでに講義資料がアップロードされていたので引用させていただきます。

細かい内容はスライドをご覧いただくとして,このブログでは補足だけ(恐れ多いですが)。

スコープ関数の定義について

講義ではKotlinの目玉機能である「拡張関数」,特にapply, let, run, withなどの関数に焦点を当てた説明に多くの時間が割かれました(実はこれらを「スコープ関数」というのですが,詳しくは後述します)。
講義資料でも触れている部分ですが,ちょっとrunのソースを見てみましょうかね。

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R = block()

Kotlinに馴染みのない人にはぱっと見難しい構文かもしれませんが,これはTという型とRという型をジェネリクスとして扱う関数宣言です。
Kotlinをはじめとする最近の型推論が可能な言語では,型推論をスムーズにするために型を後方宣言する事が多いです。

// Javaの場合
String str = "hoge";

// Kotlinの場合
val str: String = "hoge"

のような感じですね(この部分は勉強会では省略されていたんですが,私的には少し不親切に感じてしまいました)。

さて,話を戻してもう一度runの宣言を見てみると,T型のクラスにrunというメソッドを宣言しています。このとき,「T型のインスタンスに対して」メソッドを宣言しているのではなく,「T型に対して」メソッドを宣言しているのがポイントです。「T型に対して」と書くとstaticメソッドと誤解されてしまいそうですが,Kotlinにおいては,上記のような書き方は,T型すべてのインスタンスが利用できるnon staticなメソッドを宣言することになります。このように,既存のクラスにあたかもメソッドを追加できるような機能のことを,Kotlinにおいては「拡張関数」といいます。
通常Javaであれば,既存のクラスにメソッドを生やすことはできないので,そのクラスを継承した独自クラスであーだこーだすることになります。これはめっっっっっっちゃ面倒なので,Kotlinの拡張関数はとっっっっっっても便利なんです(当然,無節操に使ってしまうと混乱を招きます)。

またまた話を戻すと,上記の宣言はT型のクラスに,「引数 = block: T.() -> R」,「返り値の型 = R」なるメソッドrunを拡張したことに相当します。
この時,TRもupper bound・lower boundともに制約はないので,つまるところ「全ての型」(JavaでいうところのObject型)と読み替えることができます。結局,runメソッドはあらゆるクラスに対して利用できる拡張関数ということになります。めっちゃ強い宣言ですが,こんな短く書けてしまうんですね。

さて,「返り値の型 = R」についてはまぁいいと思うのですが,「引数 = block: T.() -> R」の部分については補足が必要だと思います(恥ずかしながら私も,勉強会で聴いたときパッとわかりませんでした)。
わかりづらいのは当然,block: T.() -> Rの部分と思います。
これは「レシーバ付きの関数リテラル」といって,「blockという関数はT型のインスタンスをレシーバとして使用できる」という特性を持ちます。

あ,「レシーバ」という聞き慣れない単語が出てきたので簡単に説明してみます。
レシーバとは,一言で言えば「その関数を呼び出されるインスタンス」を示します。……わかりづらいですね。例を挙げます。

// Kotlin記法
fun Int.sum(other: Int): Int {
    return this + other
}

val a = 1
val b = a.sum(5)
println(b) // <- 6になるはずです

このとき,関数sumInt型の拡張関数です。定義文の中でthisを使っているのが特徴で,このthisが指すのが「レシーバ」です。つまり,「拡張関数が生やされた元のインスタンス」のことを「レシーバ」と言うのです。
もしレシーバが使えないとしたら,上の関数sumはどのように書くことになるでしょう。恐らくこんな感じでしょうか。

// Java記法
static int sum(int a, int b) {
    return a + b;
}

つまりレシーバのお陰でthisを利用できるようになるので,引数を1つ省略することができるのがメリットです。そのインスタンスのメソッドやプロパティを呼び出すときはJava同様thisは省略可能なので,例えばこんな風にも書けます。

// Kotlin記法
fun String.jointWithUpperCase(other: String): String {
    return toUpperCase() + other.toUpperCase()
    // return this.toUpperCase() + other.toUpperCase() でも同様の挙動
}

レシーバをうまく使うと,とても自然な感じで拡張関数を書くことができるというのがわかっていただけたでしょうか。

さて,またまたまた話を戻してblock: T.() -> Rの部分を再考しましょう。
見た目は大変イカツいですが,実際は「T型のインスタンスをレシーバとする」ということを言っているにすぎません。つまり拡張元のT型の変数に対して,thisアクセスができるということ。ここでRは任意の型なので,結局拡張元のT型インスタンスに対して「好き勝手していいよ」という大変奔放な拡張関数ということがわかります。

最後に,使用例を見ておきましょう。

val s = "hoge".run { toUpperCase() }
println(s) //=> HOGE

ご覧いただいたように,{ }の中の関数においてはthisは省略されていますが,呼び出し元(拡張元)のインスタンス(変数)"hoge"がレシーバとして扱われており,そのtoUpperCase()した結果が変数sに返されています。

他のスコープ関数

今まで見てきたrunは「レシーバを変化させて別の変数に格納する」ときに多く使われます。
その他にも細かい動作の違いによっては,似たような関数apply, let, with, alsoなどが使われます。また,これらを特別に「スコープ関数」と呼ぶことがあります({ }で囲われた部分で一時変数のスコープを小さくできることからそう呼ばれます)。

短いですが,以上でスコープ関数についての補足を終わりにします。
「レシーバ付きの関数リテラル」,ちょっとは理解の助けになったでしょうか?

おわりに

さすがにKotlinに最初に触れてから1年近く経っているので基本的な文法はわかっていたつもりだったのですが,スコープ関数の宣言式についてまでは見たことがありませんでした。
その定義を見なおすことで今後拡張関数を効果的に使うための知見が少し増えた気がします。それを自分の中で整理するためにも,頑張ってブログを書いてみました。

盲目的に勉強会に出てもまるで時間の無駄になってしまいますが,こうやって1つでも得られるものがあるからこそ,わざわざ勉強会に参加する価値があるのでしょう。もちろん,無料で勉強会を開催してくださる主催者・講師の方には本当に頭が上がりません。

私がこうしてブログを書くことで,ネット上の誰かの助けになっていたら幸いです。

最後にもう一言だけ

ことりんかわいい。