LoginSignup
0
1

More than 1 year has passed since last update.

Kotlin公式リファレンスにみる スコープ関数 を使う動機と使い分けのパターン

Last updated at Posted at 2022-08-29

はっきりと区別して使い分ける必要もないと思います。

しかし、著名な開発者のコード、や Kotlin公式、Googleサンプルコードを見ていると、ある程度の使い分けは区別されてるように見えます。

ここでは、Kotlin 公式リファレンス がどのように案内しているか、をまとめてみます。

もちろん、例外は多くあり、同じ機能を満たしながらの記述の書き換えや入れ替えが可能ですが、公式はやはり本筋をついてるように見えます。

👉 Scope functions | Kotlin
👉 Scope Functions - Kotlin Examples: Learn Kotlin Programming By Example
👉 kotlin/Standard.kt · JetBrains/kotlin
👉 Idioms | Kotlin

apply

public inline fun <T> T.apply(block: T.() -> Unit): T {
  contract {...}
  block()
  return this
}

apply executes a block of code on an object and returns the object itself. Inside the block, the object is referenced by this. This function is handy for initializing objects.

apply は、オブジェクトに対してコードのブロックを実行し、そのオブジェクト自身を返します。ブロックの内部では、オブジェクトはthisで参照されます。
この関数は、オブジェクトを初期化するのに便利です。

Object configuration
オブジェクトの設定。

Builder-style usage of methods that return Unit
Unitを返すメソッドのBuilder的な使い方。

fun arrayOfMinusOnes(size: Int): IntArray {
  return IntArray(size).apply { fill(-1) }
}

val myRectangle = Rectangle().apply {
  length = 4
  breadth = 5
  color = 0xFAFAFA
}

「apply」は「オブジェクトの初期設定」に使う と良いです。

👉 Kotlin スコープ関数 の上手な使い分け その1 - apply

also

public inline fun <T> T.also(block: (T) -> Unit): T {
  contract {...}
  block(this)
  return this
}

also works like apply: it executes a given block and returns the object called. Inside the block, the object is referenced by it, so it's easier to pass it as an argument. This function is handy for embedding additional actions, such as logging in call chains.

also も apply と同じように動作します。与えられたブロックを実行し、呼び出されたオブジェクトを返します。ブロックの内部では、オブジェクトは it によって参照されるので、引数として渡すのは簡単です。この関数は、コールチェーンにロギングなどの追加アクションを埋め込むのに便利です。

Additional effects
追加効果。

val jake = Person("Jake", 30, "Android developer") 
  .also {                                          
    writeCreationLog(it)                         
  }

「also」は「オブジェクトを使った追加処理」に使う と良さそうです。

👉 Kotlin スコープ関数 の上手な使い分け その2 - also

with

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
  contract {...}
  return receiver.block()
}

with is a non-extension function that can access members of its argument concisely: you can omit the instance name when referring to its members.

with は非拡張関数で、引数のメンバに簡潔にアクセスできる。メンバを参照する際にインスタンス名を省略できる。

Grouping function calls on an object
オブジェクトの関数呼び出しをグループ化する。

Call multiple methods on an object instance (with)
オブジェクトのインスタンスに対して複数のメソッドを呼び出す。

with(viewBinding.recyclerView) {
  setHasFixedSize(true)
  adapter = ProductSearchAdapter()
  layoutManager =
    LinearLayoutManager(
      this@ProductSearchActivity,
      LinearLayoutManager.VERTICAL,
      false
    )
}
with(holder.binding) {
  person = items[position]
  executePendingBindings()
}

「with」は「記述の長い View などの連続操作をまとめる」 のに多く使われています。

👉 Kotlin スコープ関数 の上手な使い分け その3 - with

let

public inline fun <T, R> T.let(block: (T) -> R): R {
  contract {...}
  return block(this)
}

The Kotlin standard library function let can be used for scoping and null-checks. When called on an object, let executes the given block of code and returns the result of its last expression. The object is accessible inside the block by the reference it (by default) or a custom name.

Kotlinの標準ライブラリ関数 let は、スコープや NULL チェックに使用することができます。オブジェクト上で呼び出されると、let は与えられたコードブロックを実行し、その最後の式の結果を返します。オブジェクトはブロック内で参照 it (デフォルト) またはカスタム名でアクセスできます。

Executing a lambda on non-null objects
NULLでないオブジェクトに対してラムダを実行する。

Introducing an expression as a variable in local scope
ローカルスコープでの変数としての式の導入。

Execute if not null
nullでない場合は実行。

Map nullable value if not null
nullでない場合、nullableな値をマップする。

val value = ...
value?.let {
    ... // execute this block if not null
}
val value = ...
val mapped = value?.let { transformValue(it) } ?: defaultValue
// defaultValue is returned if the value or the transform result is null.

公式では、「let」は「null を避けてのブロック内処理」 に使うことを推しています。

👉 Kotlin スコープ関数 の上手な使い分け その4 - let

run

public inline fun <T, R> T.run(block: T.() -> R): R {
  contract {...}
  return block()
}

Like let, run is another scoping function from the standard library. Basically, it does the same: executes a code block and returns its result. The difference is that inside run the object is accessed by this. This is useful when you want to call the object's methods rather than pass it as an argument.

let と同様、run も標準ライブラリのスコープ関数です。基本的には、コードブロックを実行し、その結果を返すという点では同じです。違いは、runの内部でオブジェクトにアクセスするのがthisであることです。これは、オブジェクトを引数として渡すのではなく、そのオブジェクトのメソッドを呼び出したい場合に便利です。

Object configuration and computing the result
オブジェクトの構成と結果の算出

val service = MultiportService("https://example.kotlinlang.org", 80)
val result = service.run {
  port = 8080
  query(prepareRequest() + " to port $port")
}

上記の T.run(): R と違って非拡張関数の run もあります。

public inline fun <R> run(block: () -> R): R {
  contract {...}
  return block()
}

Running statements where an expression is required
式が必要な記述の実行。

If-not-null-else shorthand
if-not-null-elseの略記法。

val files = File("Test").listFiles()

println(files?.size ?: "empty") 

val filesSize = files?.size ?: run {
  // ...
}
println(filesSize)

?.let と組み合わせての利用が多いです。

navigationCommand.route?.let {
  popBackStack(it, false)
} ?: run {
  navigateUp()
}

「run」は「ブロック内を実行する」という意味合いが強い ので、Nullable なオブジェクトで直感的に「let」と使い分けてもいいかもしれません。

👉 Kotlin スコープ関数 の上手な使い分け その5 - run

まとめ

以下の場合は apply がいいでしょうかね?

val bundle = Bundle()
bundle.putInt("x", 1)
bundle.putInt("y", 2)

val run = Bundle().run {
  putInt("x", 1)
  putInt("y", 2)
  this
}

val let = Bundle().let {
  it.putInt("x", 1)
  it.putInt("y", 2)
  it
}

val with = with(Bundle()) {
  putInt("x", 1)
  putInt("y", 2)
  this
}

val apply = Bundle().apply {
  putInt("x", 1)
  putInt("y", 2)
}

val also = Bundle().also {
  it.putInt("x", 1)
  it.putInt("y", 2)
}

val runNE = run {
  val bundleNE = Bundle()
  bundleNE.putInt("x", 1)
  bundleNE.putInt("y", 2)
  bundleNE
}

println("bundle = $bundle")
println("run    = $run")
println("let    = $let")
println("with   = $with")
println("apply  = $apply")
println("also   = $also")
println("runNE  = $runNE")
I/System.out: bundle = Bundle[{x=1, y=2}]
I/System.out: run    = Bundle[{x=1, y=2}]
I/System.out: let    = Bundle[{x=1, y=2}]
I/System.out: with   = Bundle[{x=1, y=2}]
I/System.out: apply  = Bundle[{x=1, y=2}]
I/System.out: also   = Bundle[{x=1, y=2}]
I/System.out: runNE  = Bundle[{x=1, y=2}]

どれでもいいですね!

全部書いてみて、チームの方針と照らし合わせてみましょうか。

0
1
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
0
1