5/19/2017

KotlinをY分で学ぶ

Learn X in Y MinutesにKotlinが掲載。Swiftによく似ているSteve Yeggeも気に入っているようだ。

Kotlinは、JVM、Android、ブラウザ向けの静的型付プログラミング言語である。Javaと100%互換である。詳細はここを読んでほしい。

// 1行コメントは//で始める
/*
複数行コメントはこのように見える.
*/

// キーワード"package"は、Javaと同じように働く.
package com.learnxinyminutes.kotlin

/*
Kotlinプログラムへの入り口は"main"という名前の関数である.
関数にはコマンド行引数を含む配列が渡される.
*/
fun main(args: Array) {
    /*
    値の宣言は"var"か"val"のどちらかを使用して行われる.
    "val"は変更できないが、"var"は変更できる.
    */
    val fooVal = 10 // 後で、fooValを他の何かに変更できない
    var fooVar = 10
    fooVar = 20 // fooVarは変更できる

    /*
    ほとんどの場合、Kotlinは変数の型が何かを判断できるので、毎回変数を明示的に
    指定する必要はない.
    変数の型を次のように明示的に宣言することもできる:
    */
    val foo : Int = 7

    /*
    文字列はJavaと同じように表現できる.
    エスケープはバックスラッシュで行う.
    */
    val fooString = "文字列はここにある!"
    val barString = "改行する?\n大丈夫!"
    val bazString = "タブを追加したいですか?\t大丈夫!"
    println(fooString)
    println(barString)
    println(bazString)

    /*
    生の文字列は3重引用符(""")で区切られる.
    名前の文字列は改行やそれ以外の文字列を含むことができる.
    */
    val fooRawString = """
    fun helloWorld(val name : String) {
       println("Hello, world!")
    }
    """
    println(fooRawString)

    /*
    文字列はテンプレート式を含めることができる.
    テンプレート式はドル記号($)で始まる.
    */
    val fooTemplateString = "$fooString has ${fooString.length} characters"
    println(fooTemplateString)

    /*
    変数がnullを保持するには、nullableとして明示的に指定しなければならない.
    変数にはその型に?を加えることで、nullableとして指定できる.
    ?. 演算子を使って、nullable変数にアクセスできる.
    変数がnullの場合、?: 演算子を使って、代替値を指定できる.
    */
    var fooNullable: String? = "abc"
    println(fooNullable?.length) // => 3
    println(fooNullable?.length ?: -1) // => 3
    fooNullable = null
    println(fooNullable?.length) // => null
    println(fooNullable?.length ?: -1) // => -1

    /*
    関数はキーワード"fun"を使用して宣言できる.
    関数の引数は、関数名の後に角括弧で囲んで指定する.
    関数の引数は、オプションでデフォルト値を持つことができる.
    必要に応じて、関数の戻り型は引数の後に指定する.
    */
    fun hello(name: String = "world"): String {
        return "Hello, $name!"
    }
    println(hello("foo")) // => Hello, foo!
    println(hello(name = "bar")) // => Hello, bar!
    println(hello()) // => Hello, world!

    /*
    関数に変数値を引数として渡すには、関数パラメータにキーワード"vararg"を
    付けてマークする.
    */
    fun varargExample(vararg names: Int) {
        println("引数は ${names.size} 個の要素を持つ")
    }
    varargExample() // => 引数は 0 個の要素を持つ
    varargExample(1) // => 引数は 1 個の要素を持つ
    varargExample(1, 2, 3) // => 引数は 3 個の要素を持つ

    /*
    関数が単一の式で構成される場合、波括弧は省略できる.
    関数のボディは符号 = の後に指定する.
    */
    fun odd(x: Int): Boolean = x % 2 == 1
    println(odd(6)) // => false
    println(odd(7)) // => true

    // 戻り値の型が推測できる場合、それを指定する必要はない.
    fun even(x: Int) = x % 2 == 0
    println(even(6)) // => true
    println(even(7)) // => false

    // 関数は引数として関数を取り、関数を返すことができる.
    fun not(f: (Int) -> Boolean): (Int) -> Boolean {
        return {n -> !f.invoke(n)}
    }
    // 名前付き関数は演算子 :: を使用して引数を指定できる.
    val notOdd = not(::odd)
    val notEven = not(::even)
    // ラムダ式を引数に指定できる.
    val notZero = not {n -> n == 0}
    /*
    ラムダ式が一つしかパラメータを持たない場合、
    宣言は省略できる(-> を加える)
    そのパラメータ名は "it" になる
    */
    val notPositive = not {it > 0}
    for (i in 0..4) {
        println("${notOdd(i)} ${notEven(i)} ${notZero(i)} ${notPositive(i)}")
    }

    // キーワード "class" はクラスの宣言に使用する.
    class ExampleClass(val x: Int) {
        fun memberFunction(y: Int): Int {
            return x + y
        }

        infix fun infixMemberFunction(y: Int): Int {
            return x * y
        }
    }
    /*
    新しいインスタンスを作成するにはコンストラクタを呼び出す.
    Kotlinはキーワード "new" を持たないことに注意する.
    */
    val fooExampleClass = ExampleClass(7)
    // メンバ関数はドット記法で呼び出すことができる.
    println(fooExampleClass.memberFunction(4)) // => 11
    /*
    関数がキーワード "infix" でマークされている場合、infix表記法を使って
    呼び出すことができる.
    */
    println(fooExampleClass infixMemberFunction 4) // => 28

    /*
    データクラスはデータを保持するだけのクラスを作る簡潔な方法である.
    "hashCode"/"equals" メソッドと "toString" メソッドは自動的に生成される.
    */
    data class DataClassExample (val x: Int, val y: Int, val z: Int)
    val fooData = DataClassExample(1, 2, 4)
    println(fooData) // => DataClassExample(x=1, y=2, z=4)

    // データクラスは "copy" 関数を持つ.
    val fooCopy = fooData.copy(y = 100)
    println(fooCopy) // => DataClassExample(x=1, y=100, z=4)

    // オブジェクトは複数の変数に分解できる.
    val (a, b, c) = fooCopy
    println("$a $b $c") // => 1 100 4

    // "foo" ループで再構築
    for ((a, b, c) in listOf(fooData)) {
        println("$a $b $c") // => 1 100 4
    }

    val mapData = mapOf("a" to 1, "b" to 2)
    // しかも、Map.Entryも分解できる.
    for ((key, value) in mapData) {
        println("$key -> $value")
    }

    // "with"関数はJavaScriptの"with"文とよく似ている.
    data class MutableDataClassExample (var x: Int, var y: Int, var z: Int)
    val fooMutableData = MutableDataClassExample(7, 4, 9)
    with (fooMutableData) {
        x -= 2
        y += 2
        z--
    }
    println(fooMutableData) // => MutableDataClassExample(x=5, y=6, z=8)

    /*
    "listOf" 関数を使ってリストを作成できる.
    リストは不変である - 要素は追加や削除できない.
    */
    val fooList = listOf("a", "b", "c")
    println(fooList.size) // => 3
    println(fooList.first()) // => a
    println(fooList.last()) // => c
    // リストの要素はインデックスによってアクセスできる.
    println(fooList[1]) // => b

    // "mutableListOf"関数を使って、変更可能なリストを作ることができる.
    val fooMutableList = mutableListOf("a", "b", "c")
    fooMutableList.add("d")
    println(fooMutableList.last()) // => d
    println(fooMutableList.size) // => 4

    // "setOf"関数を使って集合を作成できる.
    val fooSet = setOf("a", "b", "c")
    println(fooSet.contains("a")) // => true
    println(fooSet.contains("z")) // => false

    // "mapOf"関数を使ってマップを作成できる.
    val fooMap = mapOf("a" to 8, "b" to 7, "c" to 9)
    // マップの値はキーでアクセスできる.
    println(fooMap["a"]) // => 8

    /*
    シーケンスは遅延評価されたコレクションを表す.
    "generateSequence"関数を使ってシーケンスを作成できる.
    */
    val fooSequence = generateSequence(1, { it + 1 })
    val x = fooSequence.take(10).toList()
    println(x) // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    // フィボナッチ数の生成のためのシーケンスの使用例:
    fun fibonacciSequence(): Sequence {
        var a = 0L
        var b = 1L

        fun next(): Long {
            val result = a + b
            a = b
            b = result
            return a
        }

        return generateSequence(::next)
    }
    val y = fibonacciSequence().take(10).toList()
    println(y) // => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

    // Kotlinはコレクションを扱うための高次関数を提供する.
    val z = (1..9).map {it * 3}
                  .filter {it < 20}
                  .groupBy {it % 2 == 0}
                  .mapKeys {if (it.key) "even" else "odd"}
    println(z) // => {odd=[3, 9, 15], even=[6, 12, 18]}

    // "for"ループは、イテレータを提供する何にでも利用できる.
    for (c in "hello") {
        println(c)
    }

    // "while"ループは他の言語と同じように動作する.
    var ctr = 0
    while (ctr < 5) {
        println(ctr)
        ctr++
    }
    do {
        println(ctr)
        ctr++
    } while (ctr < 10)

    /*
    "if" は値を返す式として使用できる.
    このため、Kotlinには三項演算子 ?: は必要ない.
    */
    val num = 5
    val message = if (num % 2 == 0) "even" else "odd"
    println("$num is $message") // => 5 is odd

    // "when"は "if-else if" チェーンの代替として利用できる.
    val i = 10
    when {
        i < 7 -> println("first block")
        fooString.startsWith("hello") -> println("second block")
        else -> println("else block")
    }

    // "when"は引数を付けて利用できる.
    when (i) {
        0, 21 -> println("0 or 21")
        in 1..20 -> println("in the range 1 to 20")
        else -> println("none of the above")
    }

    // "when"は値を返す関数として利用できる.
    var result = when (i) {
        0, 21 -> "0 or 21"
        in 1..20 -> "in the range 1 to 20"
        else -> "none of the above"
    }
    println(result)

    /*
    演算子"is"を使ってオブジェクトが特定の型かどうかを調べることができる.
    オブジェクトが型チェックに合格した場合、それを明示的にキャストせずに、
    その型として使用できる.
    */
    fun smartCastExample(x: Any) : Boolean {
        if (x is Boolean) {
            // x は自動的にBooleanにキャストされる
            return x
        } else if (x is Int) {
            // x は自動的にIntにキャストされる
            return x > 0
        } else if (x is String) {
            // x は自動的にStringにキャストされる
            return x.isNotEmpty()
        } else {
            return false
        }
    }
    println(smartCastExample("Hello, world!")) // => true
    println(smartCastExample("")) // => false
    println(smartCastExample(5)) // => true
    println(smartCastExample(0)) // => false
    println(smartCastExample(true)) // => true

    // Smartcastはwhenブロックでも動く
    fun smartCastWhenExample(x: Any) = when (x) {
        is Boolean -> x
        is Int -> x > 0
        is String -> x.isNotEmpty()
        else -> false
    }

    /*
    拡張はクラスに新しい機能を追加する方法である.
    これはC#の拡張メソッドに似ている.
    */
    fun String.remove(c: Char): String {
        return this.filter {it != c}
    }
    println("Hello, world!".remove('l')) // => Heo, word!

    println(EnumExample.A) // => A
    println(ObjectExample.hello()) // => hello
}

// EnumクラスはJavaの列挙型に似ている.
enum class EnumExample {
    A, B, C
}

/*
キーワード"object"はシングルトン・オブジェクトを作成できる.
インスタンス化はできないが、その固有のインスタンスをその名前で参照できる.
これはScalaのシングルトン・オブジェクトに似ている.
*/
object ObjectExample {
    fun hello(): String {
        return "hello"
    }
}

fun useObject() {
    ObjectExample.hello()
    val someRef: Any = ObjectExample // オブジェクト名をそのまま使用する
}

参考文献

Hacker News 123