2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

『引数無しで、this が receiver で、Unit を返す関数』について

Last updated at Posted at 2021-11-06

■ はじめに

Kotlin でプログラムを書いていると、以下のようなコードを書くことがあるが、

list.forEach {
    it.doSomething()
}

下記のように書けたら嬉しいと思うことがある。

list.forEach {
    doSomething() // list の要素を lambda のスコープにしたい
}

標準ライブラリに欲しいと思いググってみたら、forEach with elements as the receivers という形で2018年に議論が開始され、未だ答えが出ていない。

■ ネットでの議論

ネットでの議論では、スコープ関数群との一貫性を保ちつつ妥当な命名を行うという方向で試行錯誤しているように見受けられる。

onEach は Kotlin 1.1 にて別の機能が割り当てられた。https://kotlinlang.org/docs/whatsnew11.html#oneach

■ 既存のスコープ関数

スコープ関数に関しては、本家の Function selection に説明がある。

参考:ネット上に上がっている図表

■ 新機能を視野に入れた情報整理

☆ スコープ関数

既存のスコープ関数のうち拡張関数のみに対して、以下の視点を追加してみた。

  • 戻り値が Unit の場合
  • レシーバーが lambda 内の 'this' になるかどうか。
  • レシーバーが lambda の引数になるかどうか。
                               +----------------------------------------------------------+
                               | receiver is 'this' in lambda / receiver is arg of lambda |
                               +----------------------------------------------------------+
                               | yes / no         | no / yes | yes / yes | no / no        |
+-------------+----------------+------------------+----------+-----------+----------------+
|return value | context object | apply            | also     | *1        | *2             |
|             | lambda result  | run              | let      | *1        | *2             |
|             | Unit           | *3               | *3       | *3        | *2, *3         |
+-------------+----------------+------------------+----------+-----------+----------------+

*1 は、receiver が this となった状態で、closure のスコープの this を it で参照できるのはちょっとうれしいかも。以下例:

DataBindingUtil.setContentView<HogeBinding>(this, R.layout.hoge).apply {
    lifecycleOwner = this@ArtistReviewToMeActivity // ← これがめんどくさい。
}

DataBindingUtil.setContentView<HogeBinding>(this, R.layout.hoge).hoge {
    lifecycleOwner = it // ← これだとらくちん。
}

*2 は、スコープも変わらず引数も無しなので、スコープ関数にする必要がない。

*3 は、戻り値を使わなければいいだけなので、無くても実装上は困らない。(けど、戻り値を返さないという意思表示になるので保守性は上がるかも)

☆ Iterable に対する関数

Iterable に対する関数としては、下記のようになっていると思われる。

                               +----------------------------------------------------------+
                               | receiver is 'this' in lambda / receiver is arg of lambda |
                               +------------------+----------+-----------+----------------+
                               | yes / no         | no / yes | yes / yes | no / no        |
+-------------+----------------+------------------+----------+-----------+----------------+
|return value | context object | *6               | onEach   | *5        | *6             |
|             | lambda result  | *7               | map      | *5        | *6             |
|             | Unit           | *4               | forEach  | *5        | *6             |
+-------------+----------------+------------------+----------+-----------+----------------+

*4 が今回欲しい『引数無しで、this が receiver で、Unit を返す関数』。

*5 は *1 と同様であったら便利なケースもある気がする。

■ 新機能をどう作ればいいのか考えてみる

  • 既存のライブラリ関数はそのまま受け入れるしかない。
  • 既存のライブラリ関数に対して命名の一貫性を保つべき。
  • 『引数無しで、this が receiver で、Unit を返す関数』は、表の *4 の位置に配置されるので、言葉として apply や run を利用することはできない。
  • 現状のスコープ関数に Unit を返すものがないので、Iterable に対する関数についても Unit を返すことにこだわらなくてもいいのでは?また、*4 は既存の知識から推測できないという点で微妙。
  • だったら、*6 と *7 の位置に、applyEach と runEach を作ればいいんじゃね?

■ 実装例

inline fun <T> Iterable<T>.applyEach(action: T.() -> Unit): Iterable<T> =
    onEach { it.action() }

inline fun <T, R> Iterable<T>.runEach(transform: T.() -> R): List<R> =
    map { it.transform() }

■ おわりに

結局『引数無しで、this が receiver で、Unit を返す関数』は作らずに applyEachrunEach を作るという斜め上の展開となってしまいました。

forEach with elements as the receivers は、既存のライブラリの概念をそのまま使おうとするかぎりは結論が出ることはないと思う、、、。1

生きてればそういうこともあるよ。

おわり。

  1. なぜなら既存のスコープ関数にUnitを返すものがないのだから。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?