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

kotlin scope function

 

■ 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")
}

 

■ 非拡張 run

上記の 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()
}

プロパティでの実行。


private val isConversionAvailable: Boolean = run {
  val expressionType = element.analyze(BodyResolveMode.PARTIAL).getType(element)
  expressionType != null && expressionType != type &&
    expressionType.isSignedOrUnsignedNumberType() && type.isSignedOrUnsignedNumberType()
}

👉 Kotlin Examples: Learn Kotlin Programming By Example hatena-bookmark
👉 Scope functions | Kotlin hatena-bookmark
👉 Idioms | Kotlin hatena-bookmark

 

■ まとめ

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

👉 Kotlin スコープ関数 の上手な使い分け その1 - apply hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その2 - also hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その3 - with hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その4 - let hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その5 - run hatena-bookmark


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

kotlin scope function

 

■ 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でない場合は実行


val value = ...

value?.let {
    ... // execute this block if not null
}

Map nullable value if not null

nullでない場合、nullableな値をマップする


val value = ...

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

👉 Kotlin Examples: Learn Kotlin Programming By Example hatena-bookmark
👉 Scope functions | Kotlin hatena-bookmark
👉 Idioms | Kotlin hatena-bookmark

 

■ まとめ

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

let 自体が不要な場合は、AndroidStudio や IDE が知らせてくれます。

👉 Kotlin スコープ関数 の上手な使い分け その1 - apply hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その2 - also hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その3 - with hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その4 - let hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その5 - run hatena-bookmark


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

kotlin scope function

 

■ 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)

オブジェクトのインスタンスに対して複数のメソッドを呼び出す。

👉 Kotlin Examples: Learn Kotlin Programming By Example hatena-bookmark
👉 Scope functions | Kotlin hatena-bookmark
👉 Idioms | Kotlin hatena-bookmark

 

■ まとめ

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


with(binding) {
  progressBar.visibility = View.VISIBLE
  cancelButton.visibility = View.VISIBLE
  goButton.visibility = View.GONE
  seeFileButton.visibility = View.GONE
}


with(viewBinding.recyclerView) {
  setHasFixedSize(true)
  adapter = ProductSearchAdapter()
  layoutManager =
    LinearLayoutManager(
      this@ProductSearchActivity,
      LinearLayoutManager.VERTICAL,
      false
    )
}


with(holder.binding) {
  person = items[position]
  executePendingBindings()
}

with の戻り値を使わないことのほうが多く見えますが、戻り値はあります。


val windowDpSize = with(LocalDensity.current) {
  windowSize.toDpSize()
}

👉 Kotlin スコープ関数 の上手な使い分け その1 - apply hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その2 - also hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その3 - with hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その4 - let hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その5 - run hatena-bookmark


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

kotlin scope function

apply と also は似ています。

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

 

■ 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 によって参照されるので、引数として渡すのは簡単です。この関数は、コールチェーンにロギングなどの追加アクションを埋め込むのに便利です。


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

Additional effects

追加効果

👉 Kotlin Examples: Learn Kotlin Programming By Example hatena-bookmark
👉 Scope functions | Kotlin hatena-bookmark
👉 Idioms | Kotlin hatena-bookmark

 

■ まとめ

「also」は「オブジェクトに対しての追加処理」に使う と良さそうです。

あと、Singleton インスタンスの生成時のコードで見かけることが印象に強いです。


@JvmStatic
fun getInstance(context: Context): PowerSpinnerPersistence =
  instance ?: synchronized(this) {
    instance ?: PowerSpinnerPersistence().also {
      instance = it
      sharedPreferenceManager =
        context.getSharedPreferences("com.skydoves.powerspinenr", Context.MODE_PRIVATE)
    }
  }


companion object {
  @Volatile
  private var INSTANCE: MySingleton? = null
  fun getInstance(context: Context) =
    INSTANCE ?: synchronized(this) {
      INSTANCE ?: MySingleton(context).also {
        INSTANCE = it
      }
    }
}

👉 Kotlin で書きたい「正しいシングルトン(Singleton)」 hatena-bookmark

こんな使い方も公式に書いてました。

Swap two variables

2つの変数を入れ替える


var a = 1
var b = 2
a = b.also { b = a }

👉 Kotlin スコープ関数 の上手な使い分け その1 - apply hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その2 - also hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その3 - with hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その4 - let hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その5 - run hatena-bookmark


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

kotlin scope function

使い分けが難しいと言われているスコープ関数ですが。

以下、どれでも同じ結果を取得できます。


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}]

どれが書きやすいですかね。

どれが分かりやすく管理しやすいですか。

 

■ apply の便利な使い方 (公式)

実装を見てみます。


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

Kotlin 公式リファレンスには使い分けとして以下の説明が書かれてます。

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) }
}

Configure properties of an object (apply)
オブジェクトのプロパティを設定する


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

👉 Kotlin Examples: Learn Kotlin Programming By Example hatena-bookmark
👉 Scope functions | Kotlin hatena-bookmark
👉 Idioms | Kotlin hatena-bookmark

 

■ まとめ

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

コンストラクタの引数 や Builder の有無に関係なしに使えます。

使い方のイメージは Builder パターンのスタイルです。


fun newIntent(context: Context, movie: Movie): Intent {
  return Intent(context, PlaybackActivity::class.java)
    .apply {
       putExtra(MOVIE, movie)
    }
}


private val messagesSent = MutableLiveData<Int>().apply { value = 0 }
private val dinosClicked = MutableLiveData<Int>().apply { value = 0 }
private val dropText = MutableLiveData<String>().apply { value = "Drop Things Here!" }


private val paint = Paint().apply {
  color = drawColor
  isAntiAlias = true
  isDither = true
  style = Paint.Style.STROKE // default: FILL
  strokeJoin = Paint.Join.ROUND // default: MITER
  strokeCap = Paint.Cap.ROUND // default: BUTT
  strokeWidth = STROKE_WIDTH // default: Hairline-width (really thin)
}

apply ブロック内に if を使うこともできます。

👉 Kotlin スコープ関数 の上手な使い分け その1 - apply hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その2 - also hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その3 - with hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その4 - let hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その5 - run hatena-bookmark