I'm trying to convert a CompletableFuture<Optional<T>> to a Flow<T?>. The extension function I'm trying to write is
fun <T> CompletableFuture<Optional<T>>.asFlowOfNullable(): Flow<T?> =
this.toMono().map { (if (it.isPresent) it.get() else null) }.asFlow()
but it fails because asFlow() doesn't exist for nullable types, AFAICT based on its definition.
So, how do I convert CompletableFuture<Optional<T>> to Flow<T?>?
Edit 1:
Here's what I've come up with so far. Feedback appreciated.
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import java.util.Optional
import java.util.concurrent.CompletableFuture
fun <T> Optional<T>.orNull(): T? = orElse(null)
fun <T> CompletableFuture<Optional<T>>.asFlowOfNullable(): Flow<T?> = flowOf(this.join().orNull())
FYI, in my case, which is using Axon's Kotlin extension queryOptional, I can now write this:
inline fun <reified R, reified Q> findById(q: Q, qgw: QueryGateway): Flow<R?> {
return qgw.queryOptional<R, Q>(q).asFlowOfNullable()
}
I'll defer for a while creating a comment with the above pattern as the answer to allow for feedback.
Edit 2:
Since it was pointed out below that asFlowOfNullable in Edit 1 would block the thread, I'm going with this from #Joffrey for now:
fun <T> Optional<T>.orNull(): T? = orElse(null)
fun <T> CompletableFuture<Optional<T>>.asDeferredOfNullable(): Deferred<T?> = thenApply { it.orNull() }.asDeferred()
Edit 3: credit to both #Tenfour04 & #Joffrey for their helpful input. :)
To use the below extensions, you need the jdk8 coroutines library:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$1.5.0"
I'm not sure where the asFlow() function comes from that you're using, but here's a way I think would work without it. It seems a little odd to me to have a Flow of a single item, because it could just be a suspend function or if you need it as an object to pass around, a Deferred, which is intended for returning a single result and is therefore more analogous to a Future than a Flow.
fun <T> CompletableFuture<Optional<T>>.asFlowOfNullable(): Flow<T?> =
flow { emit(await().orElse(null)) }
As a suspend function:
suspend fun <T> CompletableFuture<Optional<T>>.awaitNullable(): T? =
await().orElse(null))
As a deferred:
fun <T> CompletableFuture<Optional<T>>.asDeferredNullable(): Deferred<T?> =
thenApply { it.orElse(null) }.asDeferred()
Related
I'm new to Kotlin's Arrow Framework and I have a couple of questions:
Lets suppose
fun getUser(id: Int): IO<Option<User>>
fun getCards(user: User): IO<List<Card>>
fun getUserAndCards(id: Int): IO<Option<Pair<User, List<Card>>>> = IO.fx {
when (val user = !userRepository.get(id)) {
is None -> None
is Some -> {
val cards = !cardRepository.get(user.t.id)
Some(Pair(user.t, cards))
}
}
}
How can I achieve the same functionality in an "arrow stylish" manner?
I manage to get:
fun getUserAndCards(id: Int): IO<Option<Pair<User, List<Card>>>> = IO.fx {
userRepository.get(id).bind().map { user ->
val cards = cardRepository.get(user.id).bind()
Pair(user, cards)
}
}
But I obtain Suspension functions can be called only within coroutine body in the second bind().
EDIT:
I saw this post with the same question. In the answer provided, it says The problem is that the left/none option isn't covered. But IT IS covered, when applying map to a None it is expected to obtain a None.
With the new 0.11.0 release coming up soon, the most idiomatic way would be to use Arrow Fx Coroutines.
Rewriting the example to Arrow Fx Coroutines would be:
suspend fun getUser(id: Int): Option<User>
suspend fun getCards(user: User): List<Card>
suspend fun getUserAndCards(id: Int): Option<Pair<User, List<Card>>> =
option {
val user = !userRepository.get(id)
val cards = !cardRepository.get(user.t.id)
Pair(user.t, cards)
}
Where you can now rely on a option { } DSL to extract the values from Option.
The problem is that the left/none option isn't covered. But IT IS covered, when applying map to a None it is expected to obtain a None.
You're correct that it's covered, but ! is a suspending function, and map is currently not inlined so you're not allowed to call ! inside. In the 0.11.0 release the operators from the data types in Arrow-Core are inline, to improve support for suspend functions and this would solve the Suspension functions can be called only within coroutine body error.
In other functional languages such as Haskell monad transformers are often used (OptionT), but in Kotlin using suspend is a much better fit which also has quite some performance benefits over wrapping monad transfomers.
As mentioned in the other post, you can also always use traverse or sequence to turn two containers around. Option<IO<User>> -> IO<Option<User>>
I am trying to declare two suspend methods with list of String and PublishRequest Object as parameter. But the IDE is giving error with this.
The error is either make one of the function internal or remove suspend. But i want to use coroutines inside both of them.
override suspend fun publish(publishRequests: List<PublishRequest>) {
///code
}
suspend fun publish(events: List<String>) {
///code
}
The PublishRequest Data class is internal. The issues is only coming when we add the publish(events: List) method. The code is working fine the publish(publishRequests: List)
Can you explain why it is happening ?
The problem you are facing is related to type erasure.
The types List<PublishRequest> and List<String> are erased to List<*>, as consequence, you would have a JVM signature clash.
To solve your problem you have two different solutions.
Change their names and avoid a signature clash:
suspend fun publishRequests(publishRequests: List<PublishRequest>) {}
suspend fun publishEvents(events: List<String>) {}
Use a single function with a reified type and handle the different type classes inside that function:
suspend inline fun <reified T> publish(objects: List<T>) {
when {
PublishRequest::class.java.isAssignableFrom(T::class.java) -> // it's a list of PublishRequest
T::class == String::class -> // it's a list of String
}
}
Is it possible to add extension function to all classes? I was thinking about adding it to some common base class like Object. Is it possible?
With Kotlin, Any is the super type like Object for Java.
fun Any.myExtensionFunction() {
// ...
}
And if you want to support null-receiver too:
fun Any?.myExtensionFunction() {
// ...
}
It depends on whether you want to use the type of the object in the signature (either in another argument, or in the return type). If not, use Any(?) as Kevin Robatel's answer says; but if you do, you need to use generics, e.g. (from the standard library)
inline fun <T, R> T.run(block: T.() -> R): R
inline fun <T> T.takeIf(predicate: (T) -> Boolean): T?
etc.
I get an error for this fold function;
fun <T> addAll(num: List<T>) : T = num.fold(0, {x,y -> x+y})
saying:
None of the following functions can be called with the arguments supplied.
However if I do:
fun addAll(num: List<Int>) : Int = num.fold(0, {x,y -> x+y})
It works without problems.
Is it possible to keep the function generic in order to use it for lists of both integers and doubles?
Unfortunately, Kotlin does not currently allow abstracting over all number types's operators (I believe that's to be able to use primitive numbers in the JVM runtime).
So the best you can get is something like this:
fun <T> addAll(num: List<T>, zero: T, adder: (T, T) -> T) : T =
num.fold(zero, adder)
println(addAll(listOf(1,2,3), 0, Int::plus))
println(addAll(listOf(1.0,2.0,3.0), 0.0, Double::plus))
While you unfortunately can't do it with a single generic functions, you could simply use sum which is defined for many Iterable types like Iterable<Int> and also Iterable<Double>:
val doublesSum = listOf(1.0, 2.0).sum()
val intsSum = listOf(1, 2).sum()
This would at least solve the current problem :)
Another way to do this is to use functional programming.
Only do this if you're familiar with FP and need very very generic solutions.
Here's an example using the Arrow library:
import arrow.core.identity
import arrow.data.ListKW
import arrow.data.foldable
import arrow.data.k
import arrow.typeclasses.monoid
inline fun <reified T> addAll(num: List<T>): T =
ListKW.foldable().foldMap(monoid(), num.k(), ::identity)
fun main(args: Array<String>) {
println(addAll(listOf(1, 2, 3)))
println(addAll(listOf(1.0, 2.0, 3.0)))
println(addAll(listOf("one", "two", "three")))
}
I tried to create an alias function to Flowable.flatmap() as follow, but compile error.
fun <T, R> Flowable<T>.then(mapper: Function<T, Publisher<R>>): Flowable<R> {
return flatMap(mapper)
}
The error is : One type argument expected for interface Function<out R> defined in kotlin
Have any idea? Thanks!
The flatMap takes a java.util.function.Function, the actually error is you didn't import the java.util.function.Function in your Kotlin file, but I don't suggest you use the java-8 functions because you can't take advantage of the SAM Conversions to use the lambda directly from the Kotlin code which defined with java-8 functional interface as parameter type.
You should replace Function with Function1, since the Function interface is a Kotlin marker interface only. for example:
// v--- use the `Function1<T,R>` here
fun <T, R> Flowable<T>.then(mapper: Function1<T, Publisher<R>>): Flowable<R> {
return flatMap(mapper)
}
OR use the Kotlin function type as below, for example:
// v--- use the Kotlin function type here
fun <T, R> Flowable<T>.then(mapper: (T) -> Publisher<R>): Flowable<R> {
return flatMap(mapper)
}