簡體   English   中英

如何將 rxjava2 Zip function (從 Single/Observable)的 arity 推廣到 n 可空 arguments 而不會丟失其類型?

[英]How can I generalize the arity of rxjava2 Zip function (from Single/Observable) to n Nullable arguments without lose its types?

要解決的兩個主要問題:

1) 類型檢查丟失

使用數組參數Single.zip()版本,我丟失了強類型 arguments。

2) 源參數不能為空

我不能發送可為空的源值作為Single.zip() function 的參數

3) 我想要一個替代方法,該方法采用未鍵入的Object[]

public static <T, R> Single<R> zipArray(Function<? super Object[], ? extends R> zipper, SingleSource<? extends T>... sources) ...

在 haskell 中,有一個與如何在 Haskell 中實現廣義“zipn”和“unzipn”相關的問題?

在 haskell 中,我可以使用應用函子來實現這一點:

f <$> a1 <*> a2 <*> a3 <*> a4 <*> a5 <*> a6 <*> a7 <*> a8 <*> a9 <*> a10 <*> a11

f:: Int -> Int -> Int -> Int -> Int -> Int -> Int -> String -> String -> String -> Int

a1.. a11值對應每種類型

庫中有一個類似函數的列表:

  • 帶兩個 arguments:

     public static <T1, T2, R> Single<R> zip(SingleSource<? extends T1> source1, SingleSource<? extends T2> source2,BiFunction<? super T1, ? super T2, ? extends R> zipper) { ObjectHelper.requireNonNull(source1, "source1 is null"); ObjectHelper.requireNonNull(source2, "source2 is null"); return zipArray(Functions.toFunction(zipper), source1, source2); }
  • 三個:

     public static <T1, T2, T3, R> Single<R> zip( SingleSource<? extends T1> source1, SingleSource<? extends T2> source2, SingleSource<? extends T3> source3, Function3<? super T1, ? super T2, ? super T3, ? extends R> zipper)

等等...

在所有這些情況下,都很好,因為每個參數都是輸入的。 但是有一個限制,直到 9 個單一來源

在我們的項目中,我們需要更多的資源,因為我們有很多服務需要異步(在我們的例子中是 11 個參數)。

但問題是 arguments 失去了它們的強類型,更糟糕的是,其中一些可能是Nullable

例如,我們想解決這個用例:

//Given
val bothSubscribed = CountDownLatch(2) // Change this value to 0 to run the test faster
val subscribeThreadsStillRunning = CountDownLatch(1) // Change this value to 0 to run the test faster

val service = { s1: String,
                s2: Int,
                s3: String?,
                s4: Int,
                s5: String,
                s6: String,
                s7: String,
                s8: String,
                s9: String,
                s10: String?,
                s11: String ->
    val result =
        listOf(s1, "$s2", s3 ?: "none", "$s4", s5, s6, s7, s8, s9, s10 ?: "none", s11).joinToString(separator = ";")
    Single.just("Values:$result")
}

val createSingle = { value: String ->
    Observable
        .create<String> { emitter ->
            println("Parallel subscribe $value on ${Thread.currentThread().name}")
            bothSubscribed.countDown()
            subscribeThreadsStillRunning.await(20, TimeUnit.SECONDS)
            emitter.onNext(value)
            emitter.onComplete()
        }
        .singleOrError()
        .subscribeOn(io())
}

val s1 = createSingle("v1")
val s2 = Single.just(2)
val s3 = null
val s4 = Single.just(4)
val s5 = createSingle("v5")
val s6 = createSingle("v6")
val s7 = createSingle("v7")
val s8 = createSingle("v8")
val s9 = createSingle("v9")
val s10 = null
val s11 = createSingle("v11")

//When

 val result = Single.zipArray(
    listOf(
        s1,
        s2,
        s3,
        s4,
        s5,
        s6,
        s7,
        s8,
        s9,
        s10,
        s11
    )
) { arrayResult ->
    service(
        arrayResult[0] as String,
        arrayResult[1] as String,
        arrayResult[2] as String?,
        arrayResult[3] as String,
        arrayResult[4] as String,
        arrayResult[5] as String,
        arrayResult[6] as String,
        arrayResult[7] as String,
        arrayResult[8] as String,
        arrayResult[9] as String?,
        arrayResult[10] as String
    )
}

//Then
result
    .test()
    .awaitDone(50, TimeUnit.SECONDS)
    .assertSubscribed()
    .assertValues("Values:v1;2;none;4;v5;v6;v7;v8;v9;none;v11")

如您所見,如果我這樣做可能會出現問題,例如:

arrayResult[0] as String,
arrayResult[1] as Int,
arrayResult[2] as String?,
arrayResult[3] as Int,
arrayResult[4] as String,
arrayResult[5] as String,
arrayResult[6] as String,
arrayResult[7] as String,
arrayResult[8] as String,
arrayResult[9] as String?,
arrayResult[10] as String

失敗是因為:

1) Single.zip()函數都不能將可為空的值作為參數。

2)您可以在數組中更改值的順序,它可能會因為類型檢查強制轉換而失敗

帶有 11 個參數的 function 是不干凈代碼的一個很好的例子。 相反,您應該考慮構建 model 來滿足您的需求。 像這樣,您也可以為每個參數提供有意義的名稱。

data class MyObject(...)

class MyMutableObject {
    private lateinit var param0: String
    private var param1: Int
    ...

    fun setParam0(value: String) {
        param0 = value
    }
    fun setParam1(value: Int) {
        param1 = value
    }
    ...

    fun toMyObject() = MyObject(
        param0,
        param1,
        ...
    ) 
}

有了這個 model 您可以在每個源上使用zipWith()運算符。

Single.just(MyMutableObject())
      .zipWith(source0, MyMutableObject::setParam0)
      .zipWith(source1, MyMutableObject::setParam1)
      ...
      .map(MyMutableObject::toMyObject)

如果您考慮將可空性抽象為Maybe ,您可以簡單地定義一個擴展 function 接收帶有數據或不帶數據的Maybe和 map 。

inline fun <T, U, R> Single<T>.zipWith(
        other: MaybeSource<U>,
        crossinline zipper: (T, U) -> R
) = other.zipWith(toMaybe()) { t, u -> zipper(t, u) }
         .switchIfEmpty(this)

我已經使用以下方法實現了該目標:

  1. Kotlin 擴展功能
  2. 柯里化函數(Kotlin 允許這樣做)
  3. 部分應用程序(Kotlin 也允許這樣做)
  4. Functor 和 Applicative Functors 概念(Single 和 Observable 類是 Applicative functors)
  5. 將它們混合在一起:

首先, zipOver function,對於不可為空的值:

/**
 * Returns a Single that is the result of applying the function inside the context (a Single in this case).
 * This function is curried and will be used as an Applicative Functor, so each argument will be given
 * one by one
 * @param <B> the result value type
 * @param applicativeValue
 *            a Single that contains the input value of the function
 * @return the Single returned when the function is applied to the applicative value.
 * Each application will be executed on <b>a new thread</b> if and only if the Single is subscribed on a specific scheduler
 */
infix fun <A, B> Single<(A) -> (B)>.zipOver(applicativeValue: Single<A>): Single<B> =
    Single.zip(this, applicativeValue, BiFunction { f, a -> f(a) })

然后, zipOverNullable用於 Nullable 值:

/**
 * Returns a Single that is the result of applying the function inside the context (a Single in this case).
 * This function is curried and will be used as an Applicative Functor, so each argument will be given
 * one by one
 * @param <B> the result value type
 * @param applicativeValue
 *            a Single that contains the input value of the function and it can be null
 * @return the Single returned when the function is applied to the applicative value even when
 * it is null.
 * Each application will be executed on <b>a new thread</b> if and only if the Single is subscribed on a specific scheduler
 */
infix fun <A, B> Single<(A?) -> (B)>.zipOverNullable(applicativeValue: Single<A>?): Single<B> =
    when {
        applicativeValue != null -> Single.zip(this, applicativeValue, BiFunction { f, a -> f(a) })
        else -> this.map { it(null) }
    }

我將org.funktionale.currying用於 curried curried() function

通過結合這兩者,您可以編寫:

    //Given
    val bothSubscribed = CountDownLatch(0) // Change this value to 2 to run the test slowly
    val subscribeThreadsStillRunning = CountDownLatch(0) // Change this value to 1 to run the test slowly

    val service: (String, String, String?, String, String, String, String, String, String, String?, String) -> Single<String> = { 
                    s1: String,
                    s2: Int,
                    s3: String?,
                    s4: Int,
                    s5: String,
                    s6: String,
                    s7: String,
                    s8: String,
                    s9: String,
                    s10: String?,
                    s11: String ->
        val result =
            listOf(s1, "$s2", s3 ?: "none", "$s4", s5, s6, s7, s8, s9, s10 ?: "none", s11).joinToString(separator = ";")
        Single.just("Values:$result")
    }

    val createSingle = { value: String ->
        Observable
            .create<String> { emitter ->
                println("Parallel subscribe $value on ${Thread.currentThread().name}")
                bothSubscribed.countDown()
                subscribeThreadsStillRunning.await(20, TimeUnit.SECONDS)
                emitter.onNext(value)
                emitter.onComplete()
            }
            .singleOrError()
            .subscribeOn(io())
    }

    val s1: Single<String> = createSingle("v1")
    val s2: Single<Int> = Single.just(2)
    // Here, we move the Nullable value outside, so the whole Single<String> is Nullable, and not the value inside the Single`enter code here`
    val s3: Single<String>? = null
    val s4: Single<String> = Single.just(4)
    val s5: Single<String> = createSingle("v5")
    val s6: Single<String> = createSingle("v6")
    val s7: Single<String> = createSingle("v7")
    val s8: Single<String> = createSingle("v8")
    val s9: Single<String> = createSingle("v9")
    val s10: Single<String>? = null
    val s11 = createSingle("v11")

    //When
    // Here I curry the function, so I can apply one by one the the arguments via zipOver() and preserve the types 

    val singleFunction: Single<(String) -> (String) -> (String?) -> (String) -> (String) -> (String) -> (String) -> (String) -> (String) -> (String?) -> (String) -> Single<String>> =
        Single.just(service.curried()).subscribeOn(io())

    val result = singleFunction
        .zipOver(s1)
        .zipOver(s2)
        .zipOverNullable(s3)
        .zipOver(s4)
        .zipOver(s5)
        .zipOver(s6)
        .zipOver(s7)
        .zipOver(s8)
        .zipOver(s9)
        .zipOverNullable(s10)
        .zipOver(s11)
        .flatMap { it }

    //Then
    result
        .test()
        .awaitDone(50, TimeUnit.SECONDS)
        .assertSubscribed()
        .assertValues("Values:v1;2;none;4;v5;v6;v7;v8;v9;none;v11")

然后它打印出類似的東西:

Parallel subscribe v11 on RxCachedThreadScheduler-10
Parallel subscribe v8 on RxCachedThreadScheduler-8
Parallel subscribe 4 on RxCachedThreadScheduler-4
Parallel subscribe v5 on RxCachedThreadScheduler-5
Parallel subscribe v9 on RxCachedThreadScheduler-9
Parallel subscribe 2 on RxCachedThreadScheduler-3
Parallel subscribe v6 on RxCachedThreadScheduler-6
Parallel subscribe v1 on RxCachedThreadScheduler-2
Parallel subscribe v7 on RxCachedThreadScheduler-7

現在,如果我這樣做:

    val result = singleFunction
        .zipOver(s1)
        .zipOver(s1)
        .zipOverNullable(s3)
        .zipOver(s1)
        .zipOver(s5)
        .zipOver(s6)
        .zipOver(s7)
        .zipOver(s8)
        .zipOver(s9)
        .zipOverNullable(s10)
        .zipOver(s11)
        .flatMap { it }

它將在編譯時中斷

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM