Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ohtsuchi/6473a1a6e2e3e2d515273f9a9dee309e to your computer and use it in GitHub Desktop.
Save ohtsuchi/6473a1a6e2e3e2d515273f9a9dee309e to your computer and use it in GitHub Desktop.
Kotlinスタートブック読書勉強会 #4 第6章-04 ラムダ式 (p87) 補足

Kotlinスタートブック読書勉強会 #4 第6章-04 ラムダ式 (p87) 補足


reference に記述があって、赤べこ本では割愛されている(または ver1.1 以降の)内容の引用


1. 戻り値の型

  • 例えば Swift の Closure Expression では 戻り値の型を明示的に指定(または 型推論で省略も)できたりしますが
// swift の Closure Expression
{ (parameters) -> return type in
    statements
}
  • Kotlin では Functions and Lambdas - Lambdas - Anonymous Functions に以下の事が記述されてました。
    1. ラムダ式の構文に欠けているものの1つは 戻り値の型 を指定する機能
      • (= 戻り値の型 は指定できない)
    2. 大抵の場合は 戻り値の型 は自動で型推論されるため、 戻り値の型 を指定する事は不要
    3. しかし 明示的に 戻り値の型 を指定する必要がある場合は anonymous function が使える
      • (anonymous function = 赤べこ本 p97 「無名関数」)
One thing missing from the lambda expression syntax presented above is the ability to specify the return type of the function.
In most cases, this is unnecessary because the return type can be inferred automatically.
However, if you do need to specify it explicitly, you can use an alternative syntax: an anonymous function.

2. 引数リストの ()

  • Swift や java8 では 引数 を () で囲む.
    • 場合によって () が省略可能.
  • Kotlin の場合は 引数 を () で囲むと 意味が違ってくる
    • () で囲むと, Destructuring が 実行される
  • Other - Destructuring Declarations - Destructuring in Lambdas (since 1.1) 参照
    • 引数 を () で 囲まない場合 と 囲む場合 の比較が記述されている
    • (Destructuring Declarations = 赤べこ本 p194 「分解宣言」)
{ a -> ... } // one parameter
{ a, b -> ... } // two parameters
{ (a, b) -> ... } // a destructured pair
{ (a, b), c -> ... } // a destructured pair and another parameter

例: Map.Entry<K, V>

operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
operator fun <K, V> Map.Entry<K, V>.component2() = getValue()
  • 例えば Map の forEach メソッド に渡す ラムダ式 が
    • { e -> ... } の場合
      • 引数(e) の型 = Map.Entry<K, V>.
    • { (k) -> ... } の場合
      • 引数((k))の()の中の第1引数(k) の型 = K
        • component1() (=getKey()) の戻り値
    • { (k, v) -> ... } の場合
      • 引数((k, v))の()の中の第1引数(k) の型 = K
        • component1() (=getKey()) の戻り値
      • 引数((k, v))の()の中の第2引数(v) の型 = V
        • component2() (=getValue()) の戻り値

ソース例

fun main(args: Array<String>) {
    val m = mapOf(1 to "a", 2 to "b")

    m.forEach { e -> e.pl() }; ln()             // e は Map.Entry<Int, String>
    m.forEach { (k) -> k.pl() }; ln()           // k は Int
    m.forEach { (k, v) -> k.p(); v.pl() }; ln() // k は Int, v は String
    m.forEach { (_, v) -> v.pl() }; ln()        // v は String. 未使用変数は `_`

    // 以下はコンパイルエラー (Map.Entry が `component3()` を実装していないため)
    // 「 Destructuring declaration initializer of type Map.Entry<Int, String> must have a 'component3()' function 」
    //
    // m.forEach { (k, v, x) -> k.p(); };

    println("---------")
    // for ループも同様
    for (e in m) { e.pl() }; ln()               // e は Map.Entry<Int, String>
    for ((k) in m) { k.pl() }; ln()             // k は Int
    for ((k, v) in m) { k.p(); v.pl() }; ln()   // k は Int, v は String
    for ((_, v) in m) { v.pl() }; ln()          // v は String. 未使用変数は `_`
}

fun Any.p() {
    print("${this}(${this::class.simpleName}) ")
}
fun Any.pl() {
    p(); ln()
}
fun ln() {
    println()
}

出力結果

1=a(Entry) 
2=b(Entry) 

1(Int) 
2(Int) 

1(Int) a(String) 
2(Int) b(String) 

a(String) 
b(String) 

---------
1=a(Entry) 
2=b(Entry) 

1(Int) 
2(Int) 

1(Int) a(String) 
2(Int) b(String) 

a(String) 
b(String) 

  • 例えば 赤べこ本 リスト 6.9 の 引数 i: Int または i() で囲むと コンパイルエラー になる

リスト 6.9

val square1 = { i: Int ->
    i * i
}
val square2: (Int) -> Int = { i ->
    i * i
}

 ↓ リスト 6.9 の ラムダ式 の 引数 を () で囲む -> コンパイルエラー

val square1 = { (i: Int) -> // Cannot infer a type for this parameter. Please specify it explicitly.
    i * i
}
val square2: (Int) -> Int = { (i) -> // Destructuring declaration initializer of type Int must have a 'component1()' function
    i * i
}

 

  • square2 の方は
    • Int を拡張して component1 メソッドを 生やせば コンパイルが通る
operator fun Int.component1() = this
  • square1 の方は
    • このままコンパイルを通したいだけならば、
    • component1(): Int メソッド を持つ 任意のクラス(例: Pair<Int, Any>) を 引数指定
      • するように改修すれば コンパイルは通る
        • (当初の square1 の仕様: (Int) -> Int とは意味が異なってしまうけれど)
val square1: (Pair<Int, Any>) -> Int = { (i: Int) ->
    i * i
}

(勉強会の後に追記) lambda と closure の違い

勉強会の中で議論になった lambda と closure の違い の話.

勉強会の前(8月)に Twitter 上で話題になっていたのを後から気づきました.

https://twitter.com/eduraaa/status/902066581110374401

https://twitter.com/kmizu/status/902376248554303489

そう言えば groovy も 自由変数を capture してなくても Closure って呼んでたり.

https://koji-k.github.io/groovy-tutorial/closure/index.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment