Why does the combine operator of flow works with arrays(instead of list or iterator)?
From source code:
public inline fun <reified T, R> combine(
flows: Iterable<Flow<T>>,
crossinline transform: suspend (Array<T>) -> R
): Flow<R> {
val flowArray = flows.toList().toTypedArray()
return flow {
combineInternal(
flowArray,
arrayFactory = { arrayOfNulls(flowArray.size) },
transform = { emit(transform(it)) })
}
}
I guess it's just for convenience, because most combine... methods use vararg flows: Flow<T> parameter and call combineInternal() function, and it is convenient just to pass flows to combineInternal() method without converting it to another data structure. Example of a method, which accepts vararg flows: Flow<T> parameter:
public inline fun <reified T, R> combine(
vararg flows: Flow<T>,
crossinline transform: suspend (Array<T>) -> R
): Flow<R> = flow {
combineInternal(flows, { arrayOfNulls(flows.size) }, { emit(transform(it)) })
}
Related
I'm coming from Java and am new to Kotlin. I try to understand how to use receiver type with lambdas specified as functional SAM interfaces.
Let the code speak for itself.
fun interface Delegator <T> {
fun delegate(receiver: T)
}
fun <T> invokeWithDynamicReceiver(receiver: T, fn: T.() -> Unit) = receiver.fn()
fun <T> invokeWithSamInterface(receiver: T, fn: Delegator<T>) = fn.delegate(receiver)
fun dynamicReceiver() {
invokeWithDynamicReceiver("Foo") { length } // Dynamic receiver
invokeWithSamInterface("Foo") { it.length } // Can't bind receiver as "this"
}
How do I need to change the code to use the Delegator lambda with dynamic receiver?
You can define the functions inside the Delegator as extension function, this way the receiver is passed as this to the lambda.
fun interface ExtensionDelegator <T, R> {
fun T.delegate(): R
}
fun <T, R> invokeWithExtensionSamInterface(receiver: T, fn: ExtensionDelegator<T, R>): R =
with(fn) { receiver.delegate() }
Alternatively, you can simply define the dynamic receiver with a typealias to achieve the same result.
typealias AliasDelegator<T, R> = T.() -> R
fun <T, R> invokeWithAliasSamInterface(receiver: T, fn: AliasDelegator<T, R>): R = fn.invoke(receiver)
On the use site, both approaches look the same.
fun main() {
val result = invokeWithExtensionSamInterface("Foo") { length }
println(result)
val otherResult = invokeWithAliasSamInterface("Fizz") { length }
println(otherResult)
}
How can I write a Kotlin generic function that takes a function as an argument and adds a side-effect to it? For instance,
fun something(one: Int, two: String): String { return "${one}, ${two}" }
fun somethingElse(arg: Array<String>): String { return "${arg}" }
val w1 = wrapped(::something)
w1(42, "hello")
val w2 = wrapped(::somethingElse)
w2(arrayOf("ichi", "ni"))
The following works for functions that take only a single parameter:
fun <A, R> wrapped(theFun: (a: A) -> R): (a: A) -> R {
return { a: A ->
theFun(a).also { println("wrapped: result is $it") }
}
}
To make this work with an arbitrary number of arguments, I'd need some construct that gives me the type of the argument list. Unfortunately, the Function generic can't be used since it takes only one parameter. The following does not compile:
fun <A, R> wrapped(theFun: Function<A, R>): Function<A, R> {
return { args: A ->
theFun(*args).also { println("wrapped: result is ${it}") }
}
}
Or maybe I could use varargs? Does not seem to work with lambdas. Or Kotlin reflection?
Solution using reflection:
class KFunctionWithSideEffect<R>(private val f: KFunction<R>, private val sideEffect: (R) -> Unit) : KFunction<R> by f {
override fun call(vararg args: Any?) = f.call(*args).also { sideEffect(it) }
override fun callBy(args: Map<KParameter, Any?>) = f.callBy(args).also { sideEffect(it) }
}
fun <R> wrapped(theFun: KFunction<R>, sideEffect: (R) -> Unit = { str -> println("wrapped: result is $str") }) =
KFunctionWithSideEffect(theFun, sideEffect)
Usage:
val w1 = wrapped(::something)
w1.call(42, "hello")
val w2 = wrapped(::somethingElse)
w2.call(arrayOf("ichi", "ni"))
I'm looking for suspending way writing to file. I found this example from Kotlin/coroutines-examples which is wraping AsynchronousFileChannel with suspendCoroutine { ... } but I'm wondering if there is any benefit from wrapping synchronous call with withContext(IO){}
private suspend fun File.writeTextAsync(text: String): Unit = suspendCoroutine { cont ->
val aFileChannel = AsynchronousFileChannel.open(toPath(), StandardOpenOption.WRITE)
val byteBuffer = ByteBuffer.wrap(text.toByteArray())
aFileChannel.write(
byteBuffer,
0,
Unit,
object : java.nio.channels.CompletionHandler<Int, Unit> {
override fun completed(bytesRead: Int, attachment: Unit) {
cont.resume(Unit)
}
override fun failed(exception: Throwable, attachment: Unit) {
cont.resumeWithException(exception)
}
})
}
vs.
withContext(IO) { File(...).writeText(text) }
What I want is a function like this:
suspendCoroutineWithTimeout(timeout: Long, unit: TimeUnit, crossinline block: (Continuation<T>) -> Unit)
That does basically the same thing as the existing suspendCoroutine function but if the callback or whatever was provided in the block dosen't get called within the specified timeout the corutine continues but with a TimeoutException or something like that.
You can combine withTimeout and suspendCancellableCoroutine in a straightforward way for the desired effect:
suspend inline fun <T> suspendCoroutineWithTimeout(
timeout: Long, unit: TimeUnit,
crossinline block: (Continuation<T>) -> Unit
) = withTimeout(timeout, unit) {
suspendCancellableCoroutine(block = block)
}
Perfect answer from #Roman Elizarov.. Just adding my 2 cents on it because I needed a return from that call.. So adding T? return it would be ...
suspend inline fun <T> suspendCoroutineWithTimeout(timeout: Long, crossinline block: (Continuation<T>) -> Unit ) : T? {
var finalValue : T? = null
withTimeoutOrNull(timeout) {
finalValue = suspendCancellableCoroutine(block = block)
}
return finalValue
}
If you're using suspendCoroutine, that means you have full control over what you do with the continuation you got. For example, you can pass it to the callback-based async API and, additionally, to a scheduled task that will resume it with exception:
suspend fun mySuspendFun(timeout: Long): String {
val didResume = AtomicBoolean()
fun markResumed() = !didResume.getAndSet(true)
return suspendCoroutine { cont ->
launch(CommonPool) {
delay(timeout)
if (markResumed()) {
cont.resumeWithException(TimeoutException())
}
}
// call Async API, and in the callback, use
// if (markResumed()) {
// cont.resume(result)
// }
}
}
However, Kotlin's standard library supports your use case first-class, as described in Roman Elizarov's answer. I suggest you use that approach in your project.
suspend inline fun <T> suspendCoroutineWithTimeout(
timeout: Long,
crossinline block: (CancellableContinuation<T>) -> Unit
): T? {
var finalValue: T? = null
withTimeoutOrNull(timeout) {
finalValue = suspendCancellableCoroutine(block = block)
}
return finalValue
}
suspend inline fun <T> suspendCoroutineObserverWithTimeout(
timeout: Long,
data: LiveData<T>,
crossinline block: (T) -> Boolean
): T? {
return suspendCoroutineWithTimeout<T>(timeout) { suspend ->
var observers : Observer<T>? = null
val oldData = data.value
observers = Observer<T> { t ->
if (oldData == t) {
KLog.e("参数一样,直接return")
return#Observer
}
KLog.e("参数不一样,刷新一波")
if (block(t) && !suspend.isCancelled) {
suspend.resume(t)
observers?.let { data.removeObserver(it) }
}
}
data.observeForever(observers)
suspend.invokeOnCancellation {
KLog.e("删除observiers")
observers.let { data.removeObserver(it) }
}
}
}
The previous #Roman Elizarov and #febaisi answers have been answered very well, I added a type judgment and livedata on this basis, and I will return only when the conditions are met. Sorry, my English is not very good. –
Given some
suspend fun a(): Int
This works:
launch(Unconfined) {
(1..10).forEach {
val a = a()
println("Result is $a")
}
}
But this fails at compile time:
val action: (Int) -> Unit = {
// Suspend function should be called only from a coroutine
// or another suspend function:
val a = a()
println("Result is $a")
}
launch(Unconfined) {
(1..10).forEach(action)
}
Furthermore, it isn't fixable because:
val action: suspend (Int) -> Unit = {
val a = a()
println("Result is $a")
}
launch(Unconfined) {
// suspend (Int) -> Unit cannot be applied to (T) -> Unit
(1..10).forEach(action)
}
What is the story here in terms of the static type system? The current situation looks like a quick hack where an inline block containing a suspend fun call is still inferred to a non-suspend type signature.
Is this an area where the design will be improved before being finalized?
The suspend and normal functional types are not subtypes of each other and thus cannot be assigned or passed to a function in place of each other:
val f: () -> Unit = { }
val g: suspend () -> Unit = f // Type mismatch
val f: suspend () -> Unit = { }
val g: () -> Unit = f // Type mismatch
This is why a suspend (Int) -> Unit cannot be passed to forEach.
Basically, the restriction for suspend functions to be called only in other suspend functions works irrespective to the type system. Such calls should simply be placed inside a suspend function or a suspend lambda or inlined into one. So, this should also work:
val action: suspend (Int) -> Unit = {
val a = a()
println("Result is $a")
}
launch(Unconfined) {
(1..10).forEach { action() } // The call is inlined into a suspend lambda
}
I've filed an issue about supporting (1..10).forEach(action) as well: KT-22186