前から気になってはいたとはいえ、どうせ実務では使わないだろうしなぁ、と思っていたのだけど、「KotlinがAndroidの正式サポート言語に」というニュースがあり、これはまじめに使う意味があるという気になったのでここ一週間ばかし Kotlin を触ってみている。
人によってはAndroid でサポートされるプログラミング言語がひとつ増えただけじゃないか、と思うかもしれない。しかし、Kotlin だけでなく Swift、Rust、TypeScript と型推論、不変性、関数型、Null 安全性という同じような言語概念を持つ言語が研究レベルでなく仕事で使える形で出てきた今の状況は、まさに Java という GC とオブジェクト指向を持った言語が仕事で使えるようになった時と同じようにも感じられる。
「業務システム開発の次の言語としての Kotlin の可能性」という記事もあったけれど、今それが実現しつつあるのかもしれない。
正直、どの言語も似たり寄ったりなのでもう少し収斂してほしいところではあるけれども、各社の思惑とかJavaScriptやABI、既存ライブラリとの親和性などそれぞれ多少要件が違うので仕方ないところもある。
もはや懐かしい話になりつつあるけれども、Java も20年ほど前まではメモリ管理できない言語なんて使えない、オブジェクト指向なんて必要ない、起動が遅すぎて使い物にならないと否定される一方で先進的な言語として輝かしい存在だったと記憶している(当時、Java House ML などで活発な議論が繰り広げられていた)。
Java は古臭くスクリプト言語こそが最先端という風潮もしばらく前まではしばしば見られた光景だったけれど、業務アプリ分野に限れば COBOL に代わり Java の一人勝ちという状況が続いている*1。スクリプト言語にはスクリプト言語の良さがあるけれども、セキュリティへの対応、(サーバー課金を安くするための)パフォーマンス、並列処理という昨今のコンテキストを考えれば、事前検証可能な(曖昧な言い方にはなるが)カッチリした言語が必要という認識が広まっていたように思う。
型推論の採用によりスクリプト言語のような簡潔な記述と厳密な型チェックを両立しつつ、不変性と関数型で並列処理に対応し、Null 安全性でよりチェックも進化させるという新しい言語群の特徴は、時代の変化に対応したものであり、ある種のイノベーションと呼ぶべきものだ。研究者が見れば、どの言語もあの言語のあの機能のパクリだ、と見えるかもしれないけれども、実際にはそうではない。この20年をかけて、この機能は有用だ、この機能は別のこの機能で代用できる、この機能はすごいけど複雑だからよくない、この言語概念は今なら開発者に受け入れられる、といった小さな実績の積み重ねが、今ようやく次世代言語という形で表に出てきたということだ。
閑話休題。Kotlin を触ってみて、オライリーに倣って、Good Parts / Bad Parts と言いたいところだけど、専門家でもないので、個人的に気に入ったところ、気に入らないところを書いておくに留める。
気に入ったところ
- プロパティがある
- 不変性が作りやすい。遅延評価もできる。
- 関数型が普通に使える。継承できるし、JavaScript みたいに 関数にプロパティを生やすこともできる。
- クロージャからローカル変数が更新できる(Java ができないだけという気もするけど)
- デフォルト引数が指定できる。
- 移譲による実装の再利用ができる。
- 検査付き例外がない。今となっては不利益の方が大きいでしょ。どうしても必要なら Either 作ればよいだけだし。
- Generics に不変、共変が指定できる(Java の総称型以後、C# や Scala の経験から議論が収斂し、これが一番使いやすいというね、というコンセンサスが取られた機能のひとつな気がする。is とか async/await も同じ雰囲気を感じる)
- 従来なら言語機能で実現していたものを、できるだけ通常機能で実現しようとしているところ。タプルとか try-with-resourcs とか synchronized とか。
- スコープ関数が便利すぎる。
- == でオブジェクトの比較ができる
気に入ったところは、やはり Java で困っていたところが多い気がする。プロパティや不変性は20年前のコンテキストでは全然重要だと思われていなかったので Java にないのは仕方ないところではあるけれども(JavaBeans も RAD ツールでの必要性から作られている)。
機能がありすぎて初学者は辛いだろうなぁー、とは思いながらも、欲しかった機能は大抵あるので個人的にはすごく満足している。正直、Java の Generics/Lambda は使いづらすぎるので、Kotlin に移行する意味はあると思う。
気に入らないところ
- JVM の制約を引きずってる。Array と List が共通のインターフェイスを持っていなかったりとか、ByteArray 型があったりとか。Unsigned 型がないのも同様(C言語からの移植にとても困る)。
- Null はチェックできても配列のチェックはできない(言語仕様的には範囲外アクセスで null が返ってくれば整合性取れる気がするんだけど、実装的に java.lang.Character への変換が発生してパフォーマンスが落ちるとかの問題があるのかも)
- 演算子の多重定義が拡張メソッドになっていてインターフェイスになってない。演算子が実装されてるか調べようがない。例えば、ArrayとList の両方に適用できる列挙メソッドを定義するには、一旦 iterator() を呼んで Iterator に変換する必要がある。
- 不必要にキーワードが変わってる。例えば、static → companion、switch → when とか。trait を interface に戻したのは正しい判断だと思う。
- 文字列のテンプレートが $ なのは失敗では。外で変換するのも見越して文字列内に${...}を書きたいケースは結構あると思うけどエスケープが必要となると結構困るのでは(例えば、シェルスクリプトの生成とか)。Swift の \(...) は既存のエスケープ文字を流用したという意味で慧眼だと思う。
- vararg キーワードは微妙では。Java や JavaScript ですら ... で書けるのに。むしろ退化してるような。
- 実装関連の機能はキーワードじゃなくてアノテーションにしてほしかった。例えば、infix とか const とか。趣味の問題ではあるんだけど。
- Range に 1..10 みたいな文法が割り当てられている。数値のイテレーションってもはやそんなに重要な言語機能じゃないんだから range(1, 10) で良くない?
- 関数とクロージャで文法に一貫性がない。この言語仕様なら全部 fun() {} or fun() = xxx とかで良かったんでは。短くするのが流行りというのはわからなくはないけど。
- ruby みたく false あるいは null が偽という扱いにしてほしかった。
- クラス変数の扱いが微妙。元々 Kotlin 的には static 不要という論らしい。拡張メソッドがあるから static メソッドいらないのは同意するけど、パッケージに変数やメソッド置けるからいらないでしょ、てのは納得感ない気が。実際 Int.MAX_VALUE は companion object として実装されているし。それに導入するなら static object という名前にしてほしかった。
- スコープ関数便利だけど数が多すぎて使う時に混乱する。
- 遅延評価周りはこなれていない印象。lateinit と lazy() と Delegates.notNull() の使い分けが難しい。
- JVM版に dynamic class がない。まぁ、ほとんど使わないんだけどオプション用途だと欲しくなる。Java でオプション的なAPI作る時いつも悩むのよね。遅くていいので実装してほしい(JSON っぽく初期化できるとさらにうれしい)。
- 気に入らないというほどではないんだけど、Union Type は欲しいかも。val value:(String | Number | Boolean) と書きたいケースはたまにある。すでに TypeScript では実装されているけど、JavaScript との相互変換を考えると必須機能なのかもという気もする。
JVM言語が発祥だったり、Swift とは生まれた時期の都合のあるので仕方ないところも多々あるんだけど。もしかすると、これからしばらくは Swift 同様、言語仕様の小幅な修正はあるかもしれない。