Is there an operator that allows to process result/success whether or not Mono is empty. For example:
Mono<Bar> result = sourceMono.flatMap(n -> process(n)).switchIfEmpty(process(null));
where:
Mono<Bar> process(Foo in){
Optional<Foo> foo = Optional.ofNullable(in);
...
}
is there a shortcut operator that allows something like below or similar?
Mono<Bar> result = sourceMono.shortCut(process);
More specifically, mono.someOperator() returns Optional<Foo> which would contain null when Mono is empty and have value otherwise.
I wanted to avoid to create process method as mentioned above and just have a block of code but not sure which operator can help without duplicating block.
There is no built-in operator to do exactly what you want.
As a workaround, you can convert the Mono<Foo> to a Mono<Optional<Foo>> that emits an empty Optional<Foo> rather than completing empty, and then operate on the emitted Optional<Foo>.
For example:
Mono<Bar> result = fooMono // Mono<Foo>
.map(Optional::of) // Mono<Optional<Foo>> that can complete empty
.defaultIfEmpty(Optional.empty()) // Mono<Optional<Foo>> that emits an empty Optional<Foo> rather than completing empty
.flatMap(optionalFoo -> process(optionalFoo.orElse(null)));
As per above #phil's workaround, here is a reusable function:
private final <T> Mono<Optional<T>> afterSucess(Mono<T> source) {
return source
.map(Optional::of) //
.defaultIfEmpty(Optional.empty());
}
then invoke in publisher line:
Foo<Bar> result = fooMono
.transformDeferred(this::afterSucess)
.flatMap(optionalFoo -> process(optionalFoo.orElse(null)));
Related
The following example of Kotlin source code returns an error when compiled:
fun main() {
var index: Int // create an integer used to call an index of an array
val myArray = Array(5) {i -> i + 1} // create an array to call from
val condition = true // makes an if statement run true later
if (condition) {
index = 2 // sets index to 2
}
println( myArray[index] ) // should print 2; errors
}
The error says that the example did not initialize the variable index by the time it is called, even though it is guaranteed to initialize within the if statement. I understand that this problem is easily solved by initializing index to anything before the if statement, but why does the compiler not initialize it? I also understand that Kotlin is still in beta; is this a bug, or is it intentional? Finally, I am using Replit as an online IDE; is there a chance that the compiler on the website simply is an outdated compiler?
The compiler checks whether there is a path in your code that the index may not be initialized based on all the path available in your code apart from the value of the parameters. You have an if statement without any else. If you add the else statement you will not get any compile error.
I was trying to generate all permutations of a list in Kotlin. There are a zillion examples out there which return a List<List<T>>, but my input list breaks those as they try to fit all the results in the output list. So I thought I would try to make a version returning Sequence<List<T>>...
fun <T> List<T>.allPermutations(): Sequence<List<T>> {
println("Permutations of $this")
if (isEmpty()) return emptySequence()
val list = this
return indices
.asSequence()
.flatMap { i ->
val elem = list[i]
(list - elem).allPermutations().map { perm -> perm + elem }
}
}
// Then try to print the first permutation
println((0..15).toList().allPermutations().first())
Problem is, Kotlin just seems to give up and asks for the complete contents of one of the nested sequences - so it never (or at least not for a very long time) ends up getting to the first element. (It will probably run out of memory before it gets there.)
I tried the same using Flow<T>, with the same outcome.
As far as I can tell, at no point does my code ask it to convert the sequence into a list, but it seems like something internal is doing it to me anyway, so how do I stop that?
As mentioned in the comments, you have handled the empty base case incorrectly. You should return a sequence of one empty list.
// an empty list has a single permutation - "itself"
if (isEmpty()) return sequenceOf(emptyList())
If you return an empty sequence, first will never find anything - your sequence is always empty - so it will keep evaluating the sequence until it ends, and throw an exception. (Try this with a smaller input like 0..2!)
Background info
A common pattern in some programming languages is to have a function that when called, returns the next value until the end of the finite sequence is reached, in which case it keeps returning null.
A common example in Java is this:
void printAll(BufferedReader reader) {
String line;
// Assigns readLine value to line, and then check if not null
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
It is similar to the iterator in the Iterator design pattern, but the iterator has a next(): Object and a hasNext(): Boolean, whereas the BufferedReader has no hasNext() check functionality, but only the form next(): Object?, in which the returned object can be null to mark the end of the sequence. I call functions such as next() a "next function" (or maybe "yield" function), but I don't know if there is an word for this pattern.
In Java, an expression can contain assignments, which allows constructs such as: (line = reader.readLine()) != null. This code assigns the nullable value of readLine() to line, and then check whether the value in line is not null. But Kotlin doesn't allow such constructs, because in Kotlin, an assignment is not an expression, so it cannot be used as loop condition in Kotlin.
Question
What are the possible patterns in Kotlin to loop through the finite number of values returned by a next function, such as readLine()?
(Next functions can also be found for example in ZipInputStream, to go to the next zip entry.)
I'm not simply looking for a Kotlin workaround for this problem, because I can program that myself without problems. I'm looking to explore the possible patterns so that people can select one that suits their needs.
I have found some patterns myself, which I'll post here as an answer, but there may be more patterns out there, which would be interesting to know.
I've ordered to solutions by (what I believe) the best solution in descending order.
Solution 1: using built-in generateSequence (recommended)
I just found out that Kotlin has a built-in standalone generateSequence() function (located in the kotlin.sequences package).
generateSequence { br.readLine() }
.forEach { line ->
println("Line: $line")
}
generateSequence accepts a code block that you can provide, that must generates a value. In this case, br.readLine() is the code block, and generates either a String, or null if the end is reached. generateSequence generates a sequence that internally calls readLine() when the next value is requested from the sequence, until readLine() return null, which terminates the sequence. So sequences in Kotlin are lazy: they don't read neither know all the values ahead of time, only a single readLine() is called when for example forEach processes a single line. This laziness is usually exactly what you want, because it saves memory and minimizes an initial delay. To change it to eagerly, you can append generateSequence { br.readLine() } with .toList().
Pros 1: no additional variables.
Pros 2: just one construct (generateSequence).
Pros 3: returns a Sequence, so you can chain additional methods such as filter().
Pros 4: any sign of nullability is abstracted away. (No null keywords, nor ? nor ! operators.)
Pros 5: adheres a functional programming style.
IMO, this is the cleanest solution that I've seen so far.
Solution 2: while true loop with elvis break call
while (true) {
val line = br.readLine() ?: break
println("Line: $line")
}
Pros: no additional variables.
Cons: some people don't like while-true loops and break statements.
Solution 3: do-while with safe call also
do {
val line = br.readLine()?.also { line ->
println("Line: $line")
}
} while (line != null)
Pros: no additional variables.
Cons: less readable than other solutions.
Solution 4: next before start and at end of each iteration
This is probably the most common solution for Java programmers who are new to Kotlin.
var line = br.readLine()
while (line != null) {
println("Line: $line")
line = br.readLine()
}
Cons 1: duplicated next (readLine) call and a duplicated assignment.
Cons 2: reassignable var.
Solution 5: while loop with assignment using also
This is the solution generated by IntelliJ when converting Java to Kotlin code:
var line: String?
while (br.readLine().also { line = it } != null) {
println("Line: $line")
}
Cons: line is declared as nullable, even though it can never be null inside the loop. So you'll often have to use the not-null assertion operator if you want to access members of line, which you can limit to one assertion using:
var nullableLine: String?
while (br.readLine().also { nullableLine = it } != null) {
val line = nullableLine!!
println("Line: $line")
}
Cons 1: requires not-null assertion even though it can never be null inside the loop.
Cons 2: reassignable var.
Cons 3: less readable than other solutions.
Note that if you change var line: String? to var line: String, the code still compiles, but it will throw a NPE when line becomes null, even though there are no not-null assertions used.
I have a method which returns like this!
Mono<Integer> getNumberFromSomewhere();
I need to keep calling this until it has no more items to emit. That is I need to make this as Flux<Integer>.
One option is to add repeat. the point is - I want to stop when the above method emits the first empty signal.
Is there any way to do this? I am looking for a clean way.
A built-in operator that does that (although it is intended for "deeper" nesting) is expand.
expand naturally stops expansion when the returned Publisher completes empty.
You could apply it to your use-case like this:
//this changes each time one subscribes to it
Mono<Integer> monoWithUnderlyingState;
Flux<Integer> repeated = monoWithUnderlyingState
.expand(i -> monoWithUnderlyingState);
I'm not aware of a built-in operator which would do the job straightaway. However, it can be done using a wrapper class and a mix of operators:
Flux<Integer> repeatUntilEmpty() {
return getNumberFromSomewhere()
.map(ResultWrapper::new)
.defaultIfEmpty(ResultWrapper.EMPTY)
.repeat()
.takeWhile(ResultWrapper::isNotEmpty)
}
// helper class, not necessarily needs to be Java record
record ResultWrapper(Integer value) {
public static final ResultWrapper EMPTY = new ResultWrapper(null);
public boolean isNotEmpty() {
return value != null;
}
}
I need to iterate 100's of ids in parallel and collect the result in list. I am trying to do it in following way
val context = newFixedThreadPoolContext(5, "custom pool")
val list = mutableListOf<String>()
ids.map {
val result:Deferred<String> = async(context) {
getResult(it)
}
//list.add(result.await()
}.mapNotNull(result -> list.add(result.await())
I am getting error at
mapNotNull(result -> list.add(result.await())
as await method is not available. Why await is not applicable at this place? Instead commented line
//list.add(result.await()
is working fine.
What is the best way to run this block in parallel using coroutine with custom thread pool?
Generally, you go in the right direction: you need to create a list of Deferred and then await() on them.
If this is exactly the code you are using then you did not return anything from your first map { } block, so you don't get a List<Deferred> as you expect, but List<Unit> (list of nothing). Just remove val result:Deferred<String> = - this way you won't assign result to a variable, but return it from the lambda. Also, there are two syntactic errors in the last line: you used () instead of {} and there is a missing closing parenthesis.
After these changes I believe your code will work, but still, it is pretty weird. You seem to mix two distinct approaches to transform a collection into another. One is using higher-order functions like map() and another is using a loop and adding to a list. You use both of them at the same time. I think the following code should do exactly what you need (thanks #Joffrey for improving it):
val list = ids.map {
async(context) {
getResult(it)
}
}.awaitAll().filterNotNull()