iamthevoid
07/27/2021, 6:44 AMretry
function
fun <T, R> ((T, R) -> Unit).retry() {
this(argument1, argument2)
}
My real case a bit more complicated, here just extraction for clear my question.iamthevoid
07/27/2021, 6:46 AMiamthevoid
07/27/2021, 6:47 AMinterface Function<T, R> {
fun invoke(arg: T, arg1: R)
}
iamthevoid
07/27/2021, 6:48 AMlorefnon
07/27/2021, 6:50 AMiamthevoid
07/27/2021, 6:54 AMJohannes Koenen
07/27/2021, 7:59 AMinline fun <T> retry(f: () -> T) = TODO()
retry { foo(bar) }
Notice how the bar
argument is captured, but your retry function doesn't know about it.iamthevoid
07/27/2021, 8:06 AMretry
to be called from single place. I planned to do it with few extensions
fun (() -> Unit).retry() {
this()
}
fun <T> ((T) -> Unit).retry() {
this(argument1, argument2)
}
fun <T, R> ((T, R) -> Unit).retry() {
this(argument1, argument2)
}
but stucking in arguments problemCLOVIS
07/27/2021, 9:22 AMfun <I1, I2, O> ((I1, I2) -> Result<O>).retry(arg1: I1, arg2: I2): O {
var result: Result<O>
do {
result = this(arg1, arg2)
} while (result.isFailure)
return result.getOrNull() ?: error("Impossible")
}
// Usage
fun plus(a: Int, b: Int): Result<Int> = Success(a + b)
::plus.retry(4, 5)
However with this solution, you will need to write one version per arity (number of parameters).
If you don't want to write one version per arity, then @Johannes Koenen's solution is the best one:
fun <O> retry(block: () -> Result<O>): O {
var result: Result<O>
do {
result = block()
} while(result.isFailure)
return result
}
// Usage
fun plus(a: Int, b: Int): Result<Int> = Success(a + b)
retry { plus(3, 5) }
iamthevoid
07/27/2021, 10:18 AMYoussef Shoaib [MOD]
07/27/2021, 12:06 PMval result = myFun()
if(result != SOME_EXPECTED_VALUE){
result.retry()
}
iamthevoid
07/27/2021, 12:13 PMYoussef Shoaib [MOD]
07/27/2021, 3:02 PMYoussef Shoaib [MOD]
07/27/2021, 3:02 PMfun main(){
repeat(3){
var result = fetchSomeDataRetryable("test")
println(result.value)
while(result.value != 42){
result = result.retry()
println(result.value)
}
}
println(submitJsonToServer(4, "test").value)
val failedSubmission = submitJsonToServer(6, "high id")
println(failedSubmission.value)
if(!failedSubmission.value.endsWith("200 OK")){
maxIdAllowed = 6
val successfulSubmission = failedSubmission.retry()
println(successfulSubmission.value)
}
runBlocking {
var newPostsResult = pingServerForNewPosts("hunter123", Duration.milliseconds(20), 30) // 1 in 30 chance of there being new posts
println(newPostsResult.value)
while(newPostsResult.value.isFailure) newPostsResult = newPostsResult.retry().also { println(it.value) }
}
}
Youssef Shoaib [MOD]
07/27/2021, 3:03 PMdata class RetryableResult<R>(val value: R, private val _retry: () -> R){
constructor(retry: () -> R): this(retry(), retry)
fun retry(): RetryableResult<R> = copy(value = _retry())
}
fun <R> retryableResult(retry: () -> R, unit: Unit = Unit) = RetryableResult(retry)
data class SuspendRetryableResult<R>(val value: R, private val _retry: suspend () -> R){
companion object {
operator suspend fun <R> invoke(retry: suspend() -> R) = SuspendRetryableResult(retry(), retry)
}
suspend fun retry(): SuspendRetryableResult<R> = copy(value = _retry())
}
suspend fun <R> retryableResult(retry: suspend () -> R) = SuspendRetryableResult(retry)
fun interface RetryableFunction1<P1, R> {
fun realInvoke(p1: P1): R
operator fun invoke(p1: P1): RetryableResult<R> {
return RetryableResult { realInvoke(p1) }
}
}
val <P1, R> ((P1) -> R).retryable: RetryableFunction1<P1, R> get() = RetryableFunction1(this)
@OverloadResolutionByLambdaReturnType
fun <P1, R> retryable(function: (P1) -> R) = function.retryable
fun interface RetryableFunction2<P1, P2, R> {
fun realInvoke(p1: P1, p2: P2): R
operator fun invoke(p1: P1, p2: P2): RetryableResult<R> {
return RetryableResult { realInvoke(p1, p2) }
}
}
val <P1, P2, R> ((P1, P2) -> R).retryable: RetryableFunction2<P1, P2, R> get() = RetryableFunction2(this)
fun <P1, P2, R> retryable(function: (P1, P2) -> R) = function.retryable
fun fetchSomeData(url: String): Int{
return listOf(42, 64).random()
}
val fetchSomeDataRetryable = ::fetchSomeData.retryable
var maxIdAllowed = 5
val submitJsonToServer = retryable { id: Int, newJson: String ->
"$id: $newJson returned ${if(id <= maxIdAllowed) "200 OK" else "404 NOT FOUND" }"
}
suspend fun pingServerForNewPosts(userId: String, delay: Duration, randomChanceOneIn: Int) = retryableResult {
delay(delay.inWholeMilliseconds)
println(userId)
runCatching {
if(Random.nextInt(1, randomChanceOneIn) == 1)
"New posts found"
else
TODO("No New posts")
}
}
Youssef Shoaib [MOD]
07/27/2021, 3:03 PMiamthevoid
07/27/2021, 3:06 PMYoussef Shoaib [MOD]
07/27/2021, 3:07 PMfetchSomeData
is how it should be defined if either you don't have control over the original function or if you want to expose both the function itself and the retryable version. The next one is submitJsonToServer
which exposes it as a RetryableFunction2
. The issue with this example is that it messes up named parameters and default parameters. The last one, which in my opinion is the best if you have control over the function, simply takes in the parameters normally and returns a (Suspend)RetryableResult
object which can be `retry()`ed. It preserves default and named parameters, and just IMO it looks the cleanest.
Check the playground link for the whole code and to be able to edit it and tinker around with it yourself.
Keep in mind, btw, that this implementation has certain performance overheads because it creates a lambda or 2 for each function call, and so it should really be used when you already have a performance-heavy function (like a network call or database fetch) since then the cost of the lambda becomes nothing relative to that operation.Youssef Shoaib [MOD]
07/27/2021, 3:12 PMiamthevoid
07/28/2021, 4:24 AMRetryableFunction2(this)
To be clear i even must to play with it, because i don’t understand yet how it works )iamthevoid
07/28/2021, 4:25 AM