Kotlin
melangの各noteで他言語と比較した、Kotlinの設計をまとめたページ。ここに書かれている内容は、各noteの比較表から自動で集めたものです。
型
型推論が強く、Null安全が型に組み込まれている。
val age: Int = 20val age: Int = 20
val name = "Alice" // 型推論で String
var nickname: String? = null // Nullを許容する型は明示的に分ける型推論が強力で、型注釈を省略した書き方が主流。`String`と`String?`のようにNull許容/非許容を型システムで区別するのが特徴。
型注釈の位置
postfix構文で、戻り値は式本体で定義した場合に限り推論できる。
val age: Int = 20val age: Int = 20
val name = "Alice" // 型推論でString
fun greet(name: String) = "Hello, $name" // 式本体なら戻り値の型を省略できる
fun greet2(name: String): String { return "Hello, $name" } // ブロック本体は明示が必要`name: Type`という一貫したpostfix構文。引数の型注釈は必須で推論できないが、戻り値の型は式本体(`= expr`)で定義した関数に限り推論できるという、本体の書き方によって推論範囲が変わる設計。
型変換
変換をメソッドで書き、失敗し得るものにはOrNullを付ける。
val y = x.toLong()val x: Int = 10
val y: Long = x.toLong()
val big: Long = 3_000_000_000
val z: Int = big.toInt() // 黙って切り捨てられる
val a: Int? = "42".toIntOrNull() // 失敗するとnull
val b: Int = "abc".toInt() // NumberFormatException数値変換は`toLong()`, `toInt()`のようなメソッドで書き、melangの記法と最も近い。文字列のパースには例外を投げる`toInt()`と、失敗時にnullを返す`toIntOrNull()`があり、名前で失敗の扱いを区別している。ただしnullは失敗の理由を持たず、`Long.toInt()`のような数値の縮小変換は失敗を検出しない。
暗黙の型変換
拡大変換であっても数値型間の暗黙変換はない。
val x: Int = 10val x: Int = 10
val y: Long = x // コンパイルエラー
val y: Long = x.toLong() // OKJavaと異なり、Kotlinは拡大変換であっても数値型間の暗黙変換を行わない。`toLong()`, `toDouble()`のような明示的な変換メソッドを必ず呼ぶ設計で、melangが目指す方針に最も近い言語の一つ。
Enum / Sum Type
単純な列挙とADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。を、enum classとsealed classで役割分担する。
sealed class Result
data class Success(val user: User) : Result()sealed class Result
data class Success(val user: User) : Result()
data class Failure(val message: String) : Result()
fun describe(r: Result): String = when (r) {
is Success -> "ok: ${r.user}"
is Failure -> "error: ${r.message}"
} // whenがsealedを網羅していないとコンパイルエラー単純な列挙は`enum class`、payload付きのADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。は`sealed class` / `sealed interface`と役割を分ける。`when`式はsealed階層を網羅しないとコンパイルエラーになり、enumのwhenでも同様に網羅性を検査する。
値の不変性
`val`は束縛を固定するが、参照先が不変とは限らない。
val x = ... / var x = ...val tags = mutableListOf("a")
// tags = mutableListOf("b") // コンパイルエラー: valは再代入不可
tags.add("admin") // OK: MutableListは変更できる
val ro: List<String> = tags // 読み取り専用のビュー
// ro.add("x") // コンパイルエラー
tags.add("y") // roからも見える
data class User(val name: String)
val renamed = User("alice").copy(name = "bob")`val`と`var`を使い分け、`val`を基本とするのが慣習である。`List`は読み取り専用のインターフェースで、実体が`MutableList`の場合は別の参照から変更でき、不変が保証されるわけではない。`data class`の`copy`で、新しい値を作る書き方ができる。
名前付き引数
名前付き引数と位置引数を併用でき、デフォルト引数と組み合わせてよく使われる。
fun foo(width: Int, height: Int = 0)fun foo(width: Int, height: Int = 0) {}
foo(width = 10, height = 20) // 名前付き
foo(10, height = 20) // 位置引数と併用可
foo(height = 20, width = 10) // 順序は自由名前付き引数と位置引数を併用でき、名前付きにすれば順序も自由に変えられる。デフォルト引数と組み合わせることで、オプションが多い関数を簡潔に呼び出せる。
関数の引数
引数は再代入できない。渡し方を選ぶ構文はない。
fun f(n: Int, list: MutableList<Int>)fun f(n: Int, list: MutableList<Int>) {
// n = 1 // 引数は再代入できない(コンパイルエラー)
list.add(1) // 渡されたオブジェクトの中身は変更できる
}引数は常に`val`相当で再代入できないため、引数自体を書き換えて呼び出し側に影響を与える事故は防げる。一方で、可変なコレクションを渡せば中身は変更できる。melangの「代入そのものを禁止する」方向と近く、引数を共有して渡す点も同じ。ただしmelangは、オブジェクトの中身も不変にする。
if 文/式
`if`は式。値として使うときは`else`が必須で、`else`なしの`if`はstatementとしてのみ使える。
val x = if (cond) a else bval x = if (cond) 1 else 2
// 値として使うならelseが必要
// val y = if (cond) 1 // コンパイルエラー
if (cond) {
process()
}値として使うときだけ`else`が必須になる点が、melangの設計に近い。一方で、手続き的な`if ... else`も許可されているため、`else`の用途は制限されていない。
I/O の明示
`suspend`修飾子が伝播し、非suspend関数からは呼べない。
suspend fun findUser(id: UserId): Usersuspend fun findUser(id: UserId): User { /* ... */ }
fun getUser(id: UserId): User {
findUser(id) // NG: suspend関数はsuspend文脈でしか呼べない
}
suspend fun getUser(id: UserId): User {
findUser(id) // OK
}`suspend`はコルーチンの中断可能性を示すマーカーで、I/O専用ではないが、多くのI/O処理がsuspend関数として書かれる。呼び出し元への伝播が強制される点は`io`ときわめて近いが、表現しているのは「中断されうるか」であって「I/Oを行うか」ではなく、同期的なI/O(`File.readText()`など)はsuspendなしで呼べてしまう。
言語名
フィンランドの島の名前に由来し、既存語との衝突がない固有名詞。
開発元JetBrains本社近くの島`Kotlinsaari`(コトリン島)に由来する。一般語ではないため検索性・一意性が高く、大文字・小文字の表記揺れも起きにくい。地名由来の命名は、意味を持たせずに固有の識別子を得る手段として機能する。