ScalaプログラマのためのKotlin入門

  • 40
    いいね
  • 6
    コメント

JavaプログラマのためのKotlin入門という記事があったので、マネして、ScalaプログラマのためのKotlin入門を書いてみることにしました。二番煎じとか言わないでね!

Kotlinって何?

「Kotlin が Android の公式言語になることが Goole I/O 2017 で発表されました。」らしいです。Scalaプログラマが今後Kotlinを始めることが今後多くなると思うので、Kotlinをスムーズに始められるように次の 3 点についてまとめます。

  1. Scalaとほぼ同じところ
  2. 違う考え方が必要でつまづきがちなところ
  3. Kotlinならではの不便なこと

Kotlinって何?は元記事にあるのではしょります。元記事は連載形式だそうですが、今回の記事はネタ記事なのでたぶん単発で終わります。

Kotlin は JetBrains 社が作っている JVM 上で動作する言語で、 "100% interoperable with Java (Java と完全に相互運用可能 )" を謳っています。実は、Scalaもあえて謳っていませんが同じ意味で100% interoperable with Javaです。

Kotlinの略歴については割愛します。

JetBrainsはScala用のプラグインも開発していたり、初期のリポジトリをみるとScala色が濃く、Scalaをよく研究していたことと思われます。そのため、Scalaの悪いところ・良いところを踏まえた上で、Kotlinが開発されたように思えます。Kotlinの構文はScalaに非常に類似しているので、Scalaプログラマが非常に簡単に学べるようになっていますし、Scalaプログラマが比較的抵抗なく受けいれられるようにできています。Scalaプログラマなら1〜2日あればKotlinの文法の大部分を習得できると思います。

個人的には、Scalaと比べてJavaとKotlinはあまり近くないような気がするのですが、それはおいておきます。

私の主観では、Kotlinで書くとScalaと比べて生産性が2/3くらいになったように感じます。Androidアプリを書くときには長いものに巻かれろでKotlinを使うことがあるとは思いますし、趣味ライブラリをKotlinで書くこともあると思いますが、相変わらずメイン言語はScalaになりそうだなあという感じです。

Kotlinのインストール

(以下略)

Scalaとほぼ同じところ

Kotlinは多くの概念はScalaと同じです。Java対Kotlinの比ではありません。ですから、機械的な構文の置き換えを覚えるだけで、ScalaプログラマはKotlinを書けるようになります。

代表的な類似点について、列挙します:

  • プライマリコンストラクタ
  • val/varおよび変数宣言の構文(全く同じ)
  • fun(キーワードがdefと違う)
  • Function Typeの表現形式
  • Function22制限
  • overrideキーワード
  • data class(キーワードがcaseからdataに変わった)
  • オブジェクト/コンパニオンオブジェクト
  • kotlinc.bat / kotlinc スクリプト
    • scalac.bat / scalac スクリプトから派生しています(詳細はライセンス参照)

違う点ももちろん多くあるのですが、多くはライブラリの使い方に関する問題とかだったりするので、特に障害にはならないと思います。詳しくはこちら参照。

Hello, world!

// Scala
object Main extends App {
  println("Hello, world!")
}
// Kotlin
fun main(args: Array<String>) {
  println("Hello, world!")
}

Scalaではトップレベルにはクラスかオブジェクトのいずれかが来る必要があるので、オブジェクトを定義する必要があります。しかし、Kotlinではmainメソッドのためのクラスを作る必要がありません。

また、ScalaではprintlnがPredefに属するメソッドとして定義されています。Predefに定義されているメソッドはデフォルトでインポートされるので、System.outを書く必要がありません。一方、Kotlin では printlnがトップレベル関数として定義されているので、System.outをやはり書く必要がありません。Scalaでは全てのメソッドがオブジェクトに属しているためstaticが存在しません。また、アクセス修飾子はデフォルトでpublicです。Kotlinでは関数は特定のオブジェクトに属さないので、staticメソッドに近いとも言えます。Scalaでは式の末尾の;が要りませんが、同様にKotlinでも式末の;が要りません。

演算子、リテラル、コメント

おおむねScalaと同じです。0x0F や 42L などのリテラルも使えます。Scalaにはない、1_000_000のようなアンダースコア区切りのリテラルも使えます。

変数宣言

// Scala
var s: String = "abc"
val t: String = "xyz"
// Kotlin
var s: String = "abc"
val t: String = "xyz"

ご覧のように Kotlin では Scala と同じく、変数名の後ろに型を書きます。また、 final な変数を宣言するには val 、変更可能な変数を宣言するには var を使います。要するにScalaと全く同じです(真の意味で)。Kotlin では var に優先して val を使い、どうしても再代入可能にしたい場合にだけ var を使いますが、この辺はScala使いの皆様はご存知だと思うので、今更言うことではないかもしれません。

var や val を書くのが面倒と感じられるかもしれませんが、 Kotlin でもScalaと同様にローカル型推論(Local Type Inference) によって変数宣言時に型を省略することができるため、変数宣言を Java よりも簡潔に書けることが多いです(これを型推論と言うと偉い人にげふんげふん)。

// Scala
var s = "abc"
val t = "xyz"
// Kotlin
var s = "abc"
val t = "xyz"

変数宣言時に型を省略した場合、右辺値(この場合は "abc" や "xyz" )から変数の型が推論されます。

プリミティブ型

KotlinではScala同様、プリミティブ型がなく、それに相当するものは大文字で始まります。

// Scala
var a: Int = 42
val b: Boolean = true
// Kotlin
var a: Int = 42
val b: Boolean = true

やはり1文字1句同じです。内部的にintとして扱われるといった辺りのパフォーマンス事情もScalaとKotlinはほぼ同じです。

val s = a.toString()
val s = a.toString()

制御構文

if

Scala と全く同じです。Scalaと同様に、Kotlin の if は式として使えるので、次のように if や else ブロックの中で最後に評価された式の値を直接代入することができます。この点も同じです。以下のサンプルコードはKotlinのコードでもありScalaのコードでもあります。

// Scala
val foo = if (bar < 42) {
  "abc"
} else {
  "xyz"
}
// Kotlin
val foo = if (bar < 42) {
  "abc"
} else {
  "xyz"
}

while, do-while

Scalaと同じです。

for

ここまで、Kotlinが如何にScalaに(構文的に)似ているかがわかってもらえた気がしますが、forに関しては完全に別物です。正確に言うと、Javaで言う拡張forに相当する使い方ができますが、それより応用範囲が広いです。

// Scala
for (number <- numbers) {
  println(number)
}
// Kotlin
for (number in numbers) {
  println(number)
}

ただし、untilとかはたぶんScala由来で、同じように書くことができます。

// Scala
for(i <- 0 until 100) {
  println(i)
}
// Kotlin
for (i in 0 until 100) {
  println(i)
}

このuntilは特殊な構文でなく、ただのメソッドで、0.until(100)と同じです、というところも同じです。

この他にも、様々なメソッドを使って多様なループを表現できます。

// Scala
for(i <- 10 to 0 by - 1) println(i)
for(i <- 0 until 100 by 2) println(i)
for(i <- 1 to 100) println(i)
// Kotlin
for (i in 99 downTo 0) println(i) // for (int i = 99; i >= 0; i--)
for (i in 0 until 100 step 2) println(i) // for (int i = 0; i < 100; i += 2)
for (i in 1..100) println(i) // for (int i = 1; i <= 100; i++)

match -> when

match式はKotlinではwhenになります(ダウングレードですが)。whenもmatch同様式なので次のように書くことができます。

// Scala
val s = a match {
  case 0 => "abc"
  case 1 | 2 => "def"
  case _ => "xyz"
}
// Kotlin
val s = when (a) {
  0 -> "abc"
  1, 2 -> "def"
  else -> "xyz"
}

new

KotlinではScalaと違ってコンストラクタの呼び出しにnewは不要です。

// Scala
val foo = new Foo

// Kotlin
val foo = Foo()

クラス

Kotlinでもクラスの考え方はScalaと同じです。そして、だいたい同じくらいすっきりと書くことができます。

// Scala
class Person(val firstName, val lastName, age: Int) {
  def fullName: String = firstName + " " + lastName
}
// Kotlin
class Person {
  val firstName: String
  val lastName: String
  var age: Int

  constructor(firstName: String, lastName: String, age: Int) {
    this.firstName = firstName
    this.lastName = lastName
    this.age = age
  }

  fun getFullName(): String {
    return firstName + " " + lastName
  }
}

Kotlin ではデフォルトが public なので 以下略 だいたい Scalaと同じと思っておけば間違いないです。

プロパティ

Scalaでは、引数なしのメソッドがゲッター、_=付きのメソッドがセッターという形になっていますが、Kotlinはそれを言語レベルでプロパティという形でサポートしました。

// Scala
val age = person.age
person.age = age + 1
// Kotlin
val age = person.age
person.age = age + 1

記述量という点では全く差がありません…。Kotlinのプロパティはメソッドのように処理の結果を返すこともできます。たとえば、Scalaでは

// Scala
class Person {
  ...

  def fullName: String = firstName + " " + lastName
}

となるところを、Kotlinでは…

// Kotlin
class Person {
  ...

  val fullName: String
    get() {
      return firstName + " " + lastName
    }
}

あれ?同じですね…。実のところ、Scalaではdefで定義したものをvalでオーバーライドすることができるので、プロパティ構文というのは特に要らないという事実があったりします。

コンストラクタ

Kotlin では次のようにして、プロパティの宣言とコンストラクタの宣言をまとめてやってしまうことができます。

// Kotlin
class Person(val firstName: String, val lastName: String, var age: Int) {
  ...
}

これはScala由来の機能(プライマリコンストラクタ)なので、当然、Scalaでも同じように書けます。

// Scala
class Person(val firstName: String, val lastName: String, var age: Int) {
  ...
}

一方、二つ目以降のコンストラクタ、Scala用語ではセカンダリコンストラクタですが(Kotlinでも同じように呼ぶようです)、これはちょっと記法が違います。

// Scala
class Person(val firstName: String, val lastName: String, var age: Int) {
  def this(firstName: String, lastName: String){
    this(firstName, lastName, 0)
  }

  ...
}
// Kotlin
class Person(val firstName: String, val lastName: String, var age: Int) {
  constructor(firstName: String, lastName: String) : this(firstName, lastName, 0) {
  }

  ...
}

とはいえ、これも単なるキーワードレベルの違いに過ぎないとも言えます。プライマリコンストラクタにデフォルト引数を設定することは、Scala/Kotlinともにできます。

// Kotlin
class Person(val firstName: String, val lastName: String, var age: Int = 0) {
  ...
}
// Scala
class Person(val firstName: String, val lastName: String, var age: Int = 0) {
  ...
}

メソッド

// Kotlin
class Person(val firstName: String, val lastName: String) {
  ...

  // 時間経過を表すメソッド
  fun elapse(years: Int): Int {
    age += years
    return age
  }
}
// Scala
class Person(val firstName: String, val lastName: String) {
  ...

  // 時間経過を表すメソッド
  def elapse(years: Int): Int = {
    age += years
    age
  }
}

デフォルト引数などの事情もKotlinとScalaはほぼ全く同じなので以下略。

継承

これまで、延々とScalaとKotlinが如何に似ているかを書いてきましたが、大きく異る点が継承の扱いです。Kotlinはデフォルトでfinalでopenを付けたもののみが継承可能ですが、Scalaはデフォルトでopenでfinalを付けたもののみが継承不可です。理由については、元記事にも述べられているので参考にしてください。

正直なところ、私は言語の進化としてKotlinをあまり評価していませんが、デフォルトfinalにしたことは英断だったと思います。今まで、あまりにも継承は濫用され過ぎた、そう思います。

インターフェース

インタフェースも書き方が異なるだけで考え方は Scalaと同じようにみなすことはできます(さすがにこの辺はちょっと苦しい)。

// Scala
 trait Foo {
  val bar: Int
  def baz(qux: String)
}
// Kotlin
interface Foo {
  val bar: Int
  fun baz(qux: String)
}

ジェネリクス

Kotlin でも Scala と同じようにジェネリクスが使えます。

// Kotlin
class Box<T>(var value: T)
// {} の中に何も書かないなら↑の後の {} も不要
// Scala
class Box[T](var value: T)
// {} の中に何も書かないなら↑の後の {} も不要

変性のコントロールは out , in を使って簡潔に書けます。

// Kotlin
val cat: Box<out Cat> = Box(Cat())
val animal: Box<out Animal> = cat

val animal: Box<in Animal> = Box(Animal())
val cat: Box<in Cat> = animal
// Scala
val cat: Box[_ <: Cat] = Box(Cat())
val animal: Box[_ <: Animal> = cat

val animal: Box[_ >: Animal] = Box(Animal())
val cat: Box[_ >: Cat] = animal

なお、 Kotlin では利用時ではなく、 C# や Scala のように型パラメータの宣言時に変性を決定することもできますが、Scalaで+だったのがKotlinではout、Scalaで-だったのがKotlinではinというだけの話です。キーワードを置き換えただけですね。

コレクション

Kotlin では List や Map などのコレクションが、イミュータブルなものとミュータブルなもので分かれていま…せん。正確には、Listはリードオンリー、 MutableList がミュータブルです。Kotlinではあくまでリードオンリーの型を作ったことで限定的にミュータビリティを制限しただけで、たとえば、うかつにミュータブルコレクションをリードオンリーのフィールドに入れることには危険が伴います。

// Kotlin
val a: MutableList<Int> = mutableListOf()
a.add(2)
a.add(3)
a.add(5)

val b: List<Int> = a
a.add(6)
println(b) //あ!6が入ってる!

一方、Scalaでは、ListやMapやSetについて、イミュータブル、ミュータブル、リードオンリーに型が別れており、具体的な実装はイミュータブルかミュータブルのどちらかにあります。イミュータブルなオブジェクトは他から変更されることはないので安心してオブジェクトのフィールドに格納できます。

配列

配列については、Scalaと同じく、Kolintでもジェネリクスを使った通常の表記(Array<String>など)をします。

ラムダ式

この機能をラムダ式を呼ぶのは歴史を考えるとかなり抵抗があるのですが、長いものには巻かれろ式でラムダ式と呼んでおきます。KotlinでもScalaと同じくラムダ式が使えます。区切りが=>->で微妙に似ている点に注意する必要があります。

// Scala
val squared = numbers.map{ x => x * x }
// Kotlin
val squared = numbers.map{ x -> x * x }

try - catch - finally

(どんどん投げやりになってきている) try - catch - finally も Scala と同じように使えます。KotlinもScala同様検査例外がありません。

package と import

package はファイル冒頭だけでなく途中にも宣言できたり、importをファイル途中にいきなり書けたりするScalaのような自由さはKotlinにはないですが、Java準拠と考えると割と普通です。importで別名を付けたい場合、Kotlinではasを使います。

// Kotlin
import java.util.Date
import java.sql.Date as SqlDate
// Scala
import java.util.Date
import java.sql.{Date => SqlDate}

まとめ

Kotlinは構文の見た目(ほんとに見た目)が違うだけで、ほぼScalaに対応しているのがわかると思います(それと比較すると、Kotlin-Java間はだいぶ距離があると正直思います)。いや、それどころか構文の見た目すら同じ部分が多々あります。

Kotlin を使えば、Scalaと比べてずいぶんとコードがすっきり…しない気がします。たぶん、頑張って同等くらいで、普通に書くとより冗長になりそうな。オチが付きませんでしたが、自分で思っていた以上に、KotlinとScalaの文法的な距離が近いことを確認できたのは意義があったかなと思います1

なお、この記事のネタ元であるkoherさんの記事と対比させるために、引用ではなく改変コピペをした箇所がありますが、これについてはkoherさんが不快に思われるようでしたら、表現を直したいと思います。

ただ、やはり思うのは

Scala などの他の JVM 言語と比べると Java に近い

これは贔屓のし過ぎで、Kotlinはあからさまに構文を色々Scalaからパクっていますし、記法もかなり類似しています。それをKotlinの特色かのように言うのは納得が行かないところです。


  1. ほんとに、Kotlinl版のサンプルをコピペしてそのまま動いたり、キーワード置き換えだけで動いた例が多々ありました