繁体   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