kdnakt blog

hello there.

会社でKotlin勉強会(第29回)を開催した

今週は第29回を開催しました。

次で30回目……!!

 

前回の様子はコチラ↓ 

kdnakt.hatenablog.com

 

 

[第29回の様子]

2020/09/02に第29回を開催した。

 

今回は通常どおり水曜日の夕方に開催。ただし次回からは参加メンバーのチームミーティングの都合により午後一で開催予定。

参加メンバーは自分をいれて6名!今回も大盛況と言って過言ではない。

余談だが、最近社内で別の「dojo(道場)」と呼ばれる勉強会が始まったらしく、我々もその流儀に習ってKotlin dojoと名前を変えようかと考えている。参加者には一言も伝えてないけど。本家dojoの方は30人近い参加者がいるらしいので、半年先輩にあたるこちらの勉強会も負けていられない。

 

勉強会本編の内容としては、練習問題集Kotlin Koansの第3章 Collectionsをちまちまと進めた。前回未完に終わったFoldを何とか回答しおえたところで時間ぎれ。進捗は73%となり、前回比+3%。

進捗がものすごくよい、とは言えないが、後述のとおりとても良い学びがあったのでヨシ。

 

[学んだことや疑問点]

  • Fold
    • 前回ちらっと概念にふれた畳み込みの再確認から
    • 集計方法として例にあがっていたのは積だけど、和でもなんでも二項演算ならオッケーということらしい。言われれば確かに。
    • 設問を改めて確認すると、全ての顧客によって注文された商品を抽出せよというもの。
    • 前々回の勉強会で圧倒的進捗を見せてくれたメンバーがドライバー役を務めて方針を立ててくれた
    • 前提として、データ構造はShop-Customer-Order-Productという関係性になっている
    • なので、注文された全ての商品の集合をつくり、それを初期値として、顧客ごとの注文商品と積集合をとっていけばよい
    • ということでドライバーが仕上げたコードはおよそ次の通り
// Return the set of products that were ordered by every customer
fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
    val allProducts = customers.flatMap { it.orders.flatMap { it.products } }.toSet()
    return customers.fold(allProducts, { acc, customer ->
        return acc.intersect(customer.orders.flatMap{ it.products }) })
}

// 以下のエラーでテストが通らない
// Fail: testGetProductsOrderedByAllCustomers:
// The function 'getSetOfProductsOrderedByEveryCustomer' is implemented incorrectly expected:
// <['IntelliJ IDEA Ultimate' for 199.0]>
// but was:<['IntelliJ IDEA Ultimate' for 199.0, 'WebStorm' for 49.0]>
    • コメントで記載したとおり、上記のコードではテストが通らなかった。最初に立ててくれた方針通りの実装のはずなのになぜ……?
    • 別のメンバーが手元の環境で同じようなコードを書いて動作させたところ、テストが通ったとの報告があった。そのときのコードは以下の通り。
// Return the set of products that were ordered by every customer
fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
    val allProducts = customers.flatMap { it.orders.flatMap { it.products } }.toSet()
    return customers.fold(allProducts, { acc, customer ->
        acc.intersect(customer.orders.flatMap{ it.products }) })
}
    • お分かりになるだろうか、fold()に与えられたラムダ式からreturnを消すとうまく動作した。
    • Kotlinの言語仕様に詳しいメンバーが解説してくれたところによると、関数本体のreturnと関数内部で利用されるラムダ式returnが混在すると、基本的にはどちらも関数本体のreturnとして扱われるらしい。
    • では関数内部のラムダ式returnをするにはどうすればいいかというと、return@関数名のような形で対象の関数名ラベルを付与すればよい。上記の例の場合以下のようなコードになる。
// Return the set of products that were ordered by every customer
fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
    val allProducts = customers.flatMap { it.orders.flatMap { it.products } }.toSet()
    return customers.fold(allProducts, { acc, customer ->
        return@fold acc.intersect(customer.orders.flatMap{ it.products }) })
}
    • 公式サイトには以下のように説明があった。無名関数とラムダ式の場合でもreturnの挙動が違うらしい。fmfm...

Higher-Order Functions and Lambdas - Kotlin Programming Language

One other difference between lambda expressions and anonymous functions is the behavior of non-local returns. A return statement without a label always returns from the function declared with the fun keyword. This means that a return inside a lambda expression will return from the enclosing function, whereas a return inside an anonymous function will return from the anonymous function itself.

    • また、ラベル付きreturnの書き方は何通りかあるようだった。

Returns and Jumps: break and continue - Kotlin Programming Language

    • 先ほどの回答のように、ラムダ式が渡されている関数名を記載する他に、以下のやり方がある。
// Return the set of products that were ordered by every customer
fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
    val allProducts = customers.flatMap { it.orders.flatMap { it.products } }.toSet()
    return customers.fold(allProducts, mylabel@{ acc, customer ->
        return@mylabel acc.intersect(customer.orders.flatMap{ it.products }) })
}
      • または、ラムダ式ではなく無名関数を利用する
// Return the set of products that were ordered by every customer
fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
    val all = customers.flatMap { it.orders.flatMap { it.products } }.toSet()
    return customers.fold(all, fun(acc: Set<Product>, customer: Customer): Set<Product> {
        return acc.intersect(customer.orders.flatMap{ it.products })
	})
}

 

[まとめ]

今回も引き続き練習問題集Kotlin Koansを進めた。

今回はばたばたしていて「やったー!」をやり損ねてしまったが、ラムダ式のreturnに関する理解を深めることができてとても有益だった。

来週も引き続き第3章をやっていくぞ!