[英]How can I generalize the arity of rxjava2 Zip function (from Single/Observable) to n Optional arguments without lose its types?
[英]How can I generalize the arity of rxjava2 Zip function (from Single/Observable) to n Nullable arguments without lose its types?
使用數組參數Single.zip()
版本,我丟失了強類型 arguments。
我不能發送可為空的源值作為Single.zip()
function 的參數
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
Single.zip()
函數都不能將可為空的值作為參數。帶有 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)
我已經使用以下方法實現了該目標:
首先, 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.