💨

Kotlinを布教してみる

2023/09/11に公開

文法について

いくつかあると思うが、よく使うものをピックアップしていく。

null-safetyである

TypeScript, Scala, Kotlin, Rustなどこれらのモダンな言語は、nullを安全に扱うための仕組みがある。
ちなみに、Javaは、Java8からOptionalがあるが、nullを明示的に扱うことができるようになったものの、Optionalでラップしていないと、nullの問題が解決されない。
Kotlinが参考にしたScalaでは、Option<String>となるところを、KotlinではString?としていて、TypeScript感がある。
TypeScriptも、{ a?: string }といった?を使った型定義があるが、Kotlinでは型のほうに?をつけるところが、:より右側の型定義の箇所にだけ型定義を書いているので、統一感がある文法だと思う。

データクラスがある

Scalaのcase classのようなものがある。Pythonにも@dataclassがある
データ格納を前提としたクラスに付与する。
.equals(), .hashCode(), .toString() componentN()メソッドが生成される。copy メソッドも使えるようになる。

実行時にジェネリクスも含め型情報が取れる

Javaでは、型消去によりジェネリクスの型情報が取れなかったが、Kotlinでは、実行時に型情報が取れる。
※Javaでは、ジェネリクスの型情報が取れなかった。これを解決するために、List<TYPE>という型の場合、1要素目の値をとって、.getClass()を呼んで型情報を取得するということをやった記憶がある。

活用例: ジェネリクスをうまく活用して、呼ぶ側が型を意識せずに、ダミー値をセットする関数を作れる。

inline fun <reified T> dummy(): T {
    return when (T::class) {
        Int::class -> Random.nextInt()
        String::class -> "str" + Random.nextInt()
        else -> throw IllegalArgumentException("not supported type")
    } as T
}

data class Data(
    val int: Int,
    val str: String,
)
Data(dummy(),dummy())

これをうまく使ったDIのライブラリがあったりする。KoinとかKodeinとか。

if式がある

三項演算子が無く、代わりにif式を使う。これでRubyで発生していた、「ifではなく三項演算子を使いましょう」という不毛な議論(※Rubyでは、if式があるのに三項演算子もある)がなくなる。

val a = if (flg) 1 else 2

when式がある

switch文の代わりにwhen式がある。
これで、elsifを連続させて書いていたPython使いの人がJavaScriptやJavaでelse ifを連続させて書いて、「switch文を使いましょう」という不毛な議論がなくなる。
※switch-caseには、breakの書き忘れの問題もあったりする
ただのswitch-caseの変化系というだけでない。switch-caseは、値と一致しているものを実行する構文だが、条件式を書けるので、if-elseの代わりにもなる。

式志向の考え方がラムダにある

ラムダ内では、式志向なため、returnは基本的には書かない

list.map { func(it.name.uppercase()) }

Kotlinでは、ステートメントが1行だろうと、複数行だろうと、書き方が統一されている。
JavaScript等では、ステートメントが1行の関数オブジェクトの場合にreturnを書いたら、「returnを書くな」という不毛な議論が発生することがあったが、それがなくなった。

// ステートメントが1行の場合に、returnを書く書かないの違いがある
list.map(x => func(x.name.toUpperCase()))
list.map(x => {
    return func(x.name.toUpperCase())
})

拡張メソッドでメソッドを生やせる

Kotlinでは、メソッドを生やすのに、以下のように書く。
見た目的にも、Stringからメソッドが生えている感がある。

fun String.logging() {
    println("[${java.time.LocalDateTime.now()}] $this")
}

fun main() {
    "hello".logging()
}

ちなみに、Scalaでは、以下のように書く。Stringが引数であることが明示的になっている。

Scala3

extension (s: String)
  def logging(): Unit = 
    println(s"[${java.time.LocalDateTime.now()}] $s")

@main def runApp(): Unit = 
  "hello".logging()

Scala2

implicit class StringExtensions(s: String) {
  def logging(): Unit = {
    println(s"[${java.time.LocalDateTime.now()}] $s")
  }
}

object MyApp extends App {
  "hello".logging()
}

Javaという拡張性のある基盤

Javaには以下特徴があるため、拡張性が高い。そのため、ScalaやKotlinといった言語が生まれることができた。

  • Javaの標準ライブラリが、シンプルさを保っていて、リッチなAPIは、サードパーティとして提供されている。
  • いろいろなものがinterfaceとして定義されていて、実装を渡すことで、様々なライブラリを柔軟に組み合わせて使える。
  • JVM(Java Virtual Machine)が中間言語で動く。Java以外の言語でも、JVM上で動くものを作れる。
  • 後方互換性が高い。ScalaやKotlinからJavaのライブラリを呼び出すこともできる。

Scalaとの大まかな比較

簡易な文法

Scalaから影響を受けているものの、Scalaでやっていたことを親しみやすいシンタックスシュガー的な文法へと変えている。
Scalaのほうが、一貫性のある文法だと感じるものもあるが、Kotlinは、とっつきやすさとのバランス感をうまく取っていると思う。

要らない機能は削っている

学習コストが高く、そもそもあまり使わないと思われる機能(例えば、implicit val, implicit conversion, 抽出子, for内包表記, 自分型アノテーションなど)は削っている。
これにより、開発で、学習コストが高い機能に立ち向かう必要が無くなる。

いろいろな意味で安定している

  • Scala2とScala3で、対応しているライブラリが異なり、選択肢が狭まる。
  • Scalaは、関数型プログラミングとオブジェクト指向プログラミングの特性を統合した言語であるがゆえに機能が多く、多様性が生まれている。そのため、チーム開発においては、どうするか検討が必要。
GitHubで編集を提案

Discussion