アプリ開発備忘録

PlayStationMobile、Android、UWPの開発備忘録

【Kotlin】ライブラリにInterfaceと同じ大文字から始まる関数がある理由

環境

Kotlin Coroutine 1.4.2

問題

このような関数があります。実装は StateFlowImpl ですが、わざわざ MutableStateFlow という関数を作ってそこから作成させるようになっています。

@Suppress("FunctionName")
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)

理由

ドキュメントには以下のように書いてあります。

Not stable for inheritance
The MutableStateFlow interface is not stable for inheritance in 3rd party libraries, as new methods might be added to this interface in the future, but is stable for use. Use the MutableStateFlow() constructor function to create an implementation.
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-state-flow/

将来新しいメソッドが実装されるかもしれない的な事が書いてあります。

StateFlowImpl の継承しているものを見てみると以下のようになっています。

private class StateFlowImpl<T>(
    initialState: Any // T | NULL
) : AbstractSharedFlow<StateFlowSlot>(), MutableStateFlow<T>, CancellableFlow<T>, FusibleFlow<T> {
}

色々継承していますが、MutableStateFlow 関数はあくまで MutableStateFlow インターフェースとして返しているので、他に継承が追加されたとしても問題ないというような事でしょうか。
逆に勝手に色々キャストして使っても保証しないぞという事かなと。

もう一点

見るとコンストラクタの引数がAnyになっています。そういう部分が隠しやすいという利点もあるのかなと勝手に思っています。

private class StateFlowImpl<T>(
    initialState: Any // T | NULL
) : AbstractSharedFlow<StateFlowSlot>(), MutableStateFlow<T>, CancellableFlow<T>, FusibleFlow<T> {
}

更に MutableSharedFlow 関数のの実装はこのようになっています。こちらも値を加工してから格納しています。

@Suppress("FunctionName", "UNCHECKED_CAST")
public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
    require(replay >= 0) { "replay cannot be negative, but was $replay" }
    require(extraBufferCapacity >= 0) { "extraBufferCapacity cannot be negative, but was $extraBufferCapacity" }
    require(replay > 0 || extraBufferCapacity > 0 || onBufferOverflow == BufferOverflow.SUSPEND) {
        "replay or extraBufferCapacity must be positive with non-default onBufferOverflow strategy $onBufferOverflow"
    }
    val bufferCapacity0 = replay + extraBufferCapacity
    val bufferCapacity = if (bufferCapacity0 < 0) Int.MAX_VALUE else bufferCapacity0 // coerce to MAX_VALUE on overflow
    return SharedFlowImpl(replay, bufferCapacity, onBufferOverflow)
}

一回関数を挟めば加工が楽という利点もあるとか思います。
色々加工してからプライマリコンストラクタに値を詰めたい場合、 constructor() : this() で加工する暇(ブロック)がありません。以下のようにブロックを作ったり関数に分けたり等で対応ができますが、複数の引数の値が互いに影響を及ぼす等、複雑な場合に処理が冗長になります。一回関数を挟めばこれも楽に解決されます。

class Hoge(val value: String, val value2: String) {
    constructor(value: String) : this(
        value = {
            "なんか加工したい"
        }.invoke(),
        value2 = hoge(value)
    ) {

    }

    companion object {
        fun hoge(value: String): String = "なんか加工したい"
    }
}

正直これに関してはJava的に問題ないと思うのでKotlinの言語アップデートで対応してほしいですね。
こういう事がやりたい。

constructor() {
    this()
}

おわりに

正直あまり良くわかっていない部分がありますが、一回関数を通したほうが引数の変更が容易なのかなと思っています。プライマリ、セカンダリコンストラクタはだいぶ複雑だと思うので。

追記

Factory Function と言われていて、公式ドキュメントにあるそうです。