How can I generalize the arity of rxjava2 Zip function (from Single/Observable) to n Nullable arguments without lose its types?
1)类型检查丢失
使用数组参数
2)源参数不能为空
我无法发送可空的源值作为
3)我想要替代未输入
1 | public static <T, R> Single<R> zipArray(Function<? super Object[], ? extends R> zipper, SingleSource<? extends T>... sources) ... |
在haskell中,存在一个相关的问题:如何在Haskell中实现广义的" zipn "和" unzipn "?:
在haskell中,我可以使用适用的函子来实现:
1 | f <$> a1 <*> a2 <*> a3 <*> a4 <*> a5 <*> a6 <*> a7 <*> a8 <*> a9 <*> a10 <*> a11 |
存在
每种类型对应的
和
库中有类似功能的列表:
-
具有两个参数:
1
2
3
4
5public 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);
} -
包含三个:
1
2
3
4public 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个参数)。
但是问题是参数失去了它们的强类型,更糟糕的是,其中一些可能为Nullable
例如,我们要解决此用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | //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") |
如您所见,如果我这样做,可能会出现问题:
1 2 3 4 5 6 7 8 9 10 11 |
失败,因为:
1)
2)您可以在数组中更改值的顺序,由于类型检查强制转换,它可能会失败
具有11个参数的函数是不干净代码的一个很好的示例。相反,您应该考虑建立一个模型来满足您的需求。这样,您还可以为每个参数提供有意义的名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
有了此模型,您只需在每个源上使用
1 2 3 4 5 | Single.just(MyMutableObject()) .zipWith(source0, MyMutableObject::setParam0) .zipWith(source1, MyMutableObject::setParam1) ... .map(MyMutableObject::toMyObject) |
如果您考虑将可空性抽象为
1 2 3 4 5 | 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 2 3 4 5 6 7 8 9 10 11 12 | /** * 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 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 a new thread if and only if the Single is subscribed on a specific scheduler */ infix fun <A, B> Single<(A) -> (B)>.zipOver(applicativeValue: Single<A>): Single = Single.zip(this, applicativeValue, BiFunction { f, a -> f(a) }) |
然后,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * 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 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 a new thread if and only if the Single is subscribed on a specific scheduler */ infix fun <A, B> Single<(A?) -> (B)>.zipOverNullable(applicativeValue: Single<A>?): Single = when { applicativeValue != null -> Single.zip(this, applicativeValue, BiFunction { f, a -> f(a) }) else -> this.map { it(null) } } |
我将org.funktionale.currying用于
通过合并这两个,您可以编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | //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") |
然后它打印出类似以下内容的内容:
1 2 3 4 5 6 7 8 9 | 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 |
现在,如果我这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 } |
它将在编译时中断