I am trying to look more into the awaitAll of coroutines as I have a few DB calls that I would like to all launch and wait until theyre all done to aggregate the result.
However, the tricky part I am finding with the current awaitAll implementation is that it just returns a list. I wonder how do I find the original requested call if I wanted to act differently on each result?
My current implementation (without knowing who is the source of deferred task)
val dbClient = ArrayList<String>
val dbCallTasks = ArrayList<Deferred<Map<String, Set<String>>?>>()
withContext(IO) {
for (client in dbClients){
dbClient.add(client.getName())
val deferredTask = async {
withTimeoutOrNull(1000L) {
client.getItems(instanceList)
}
}
doCallTasks.add(deferredTask)
}
dbCallTasks.awaitAll()
}
But this doesnt really tell me who the results are from which client in dbClients... I dont see much implementation in the awaitAll except for calling it against a
kotlin.collections.Collection<kotlinx.coroutines.Deferred<T>>.awaitAll()
There is this implementation
awaitAll(vararg deferreds: kotlinx.coroutines.Deferred<T>): kotlin.collections.List<T>
where this would be I guess aggregating a list of Deferred into a List, does the List output guarantee ordering? Theoretically I would assume it does but that also means if I were to loop through the entries for my Map<String, Set<>> it should also be the same ordering on the second time around to find the result.
EDIT:
So I decided to use 2 lists, if awaitAll() is ordered. That means my insertion into the list, where each index i should correspond from the name of the client to the deferred job submitted.
I can't tell if I am writing the UT incorrectly, but cant seem to repro this properly which makes me think it isnt guaranteed ordering.
coEvery { dbClient.getName() } returns "A" andThen "B" andThen "C"
coEvery { dbClient.getItems(any()) } returns aHashMap andThen bHashMap andThen cHashMap
Each time I run this iteration, my results comes out differently for the result of the awaitAll()
[A, B, C]
[{}, {C=[i-DontCare]}, {}]
[A, B, C]
[{C=[i-DontCare]}, {}, {}]
And sometimes it is in the correct spot. Which makes me think it is more randomized.
[A, B, C]
[{}, {}, {C=[i-DontCare]}]
Or maybe there is more complex logic to testing async code.
Related
We have an API service call that returns a bunch of validation messages. In each message there is a string that contains an error code.
Our implementation converts the validation string into an enum value and then we process the enumeration as there are some error code we just don't care about.
The question becomes, how to handle the loop of messages in a Kotlin way:
response.validationErrors?.forEach {
val mediaFailure = decodeValidationMessage(it.message)
if (mediaFailure != MediaFailure.Unknown) {
return when (mediaFailure) {
MediaFailure.Encrypted -> DomainResponse(ErrorReasonCode.ERR_DOCUMENT_ENCRYPTED)
MediaFailure.NotSupported -> Response.validationFailed()
MediaFailure.InternalError -> Response.serviceFailed()
else -> throw NotImplementedError()
}
}
}
Here we loop through all the messages, then once the message error is not "Unknown" it returns the necessary response to the caller.
However, IntelliJ wants the else path, even though the if prevents that from happening.
Is there a proper Kotlin way of implementing this kind of loop?
From what I understood, you want to return a response for the first mediaFailure which is not MediaFailure.Unknown and you don't want that throw NotImplementedError() part in your function.
One way to fix this is to remove the if condition and continue the forEach loop when MediaFailure.Unknown is found.
response.validationErrors?.forEach {
val mediaFailure = decodeValidationMessage(it.message)
return when (mediaFailure) {
MediaFailure.Encrypted -> DomainResponse(ErrorReasonCode.ERR_DOCUMENT_ENCRYPTED)
MediaFailure.NotSupported -> Response.validationFailed()
MediaFailure.InternalError -> Response.serviceFailed()
MediaFailure.Unknown -> return#forEach // continue the loop
}
}
I think this is one of the many cases when it pays to step back from the code a bit and try to look at the big picture. To ask “What's the ultimate goal here? What am I trying to achieve with this code?”
(In traditional, lower-level languages, almost anything you want to do with a list or array requires a loop, so you get into the habit of reaching for a for or while without thinking. But there are often alternative approaches in Kotlin that can be more concise, clearer, and harder to get wrong. They tend to be more about what you're trying to achieve, rather than how.)
In this case, it looks you want to find the first item which decodes to give a known type (i.e. not MediaFailure.Unknown), and return a value derived from that.
So here's an attempt to code that:
val message = response.validationErrors?.asSequence()
?.map{ decodeValidationMessage(it.message) }
?.firstOrNull{ it != MediaFailure.Unknown }
return when (message) {
MediaFailure.Encrypted -> DomainResponse(ErrorReasonCode.ERR_DOCUMENT_ENCRYPTED)
MediaFailure.NotSupported -> Response.validationFailed()
MediaFailure.InternalError, null -> Response.serviceFailed()
else -> throw NotImplementedError()
}
This is still fairly similar to your code, and it's about as efficient. (Thanks to the asSequence(), it doesn't decode any more messages than it needs to.) But the firstOrNull() makes clear what you're looking for; and it's obvious that you go on to process only that one message — a fact which is rather lost in the original version.
(If there are no valid messages, message will be null and so this will return serviceFailed(), as per comments.)
There are of course many ways to skin a cat, and I can think of several variations. (It's often a worthwhile exercise to come up with some — if nothing else, it gives you more confidence in the version you end up with!) Try to pick whichever seems clearest, simplest, and best matches the big picture of what you're doing; that tends to work out best in the long run.
The move to the new continuations API in Arrow brought with it a handy new function: shift, in theory letting me get rid of ensure(false) { NewError() } or NewError().left().bind() constructs.
But I'm not sure how to properly use it. The documentation states that it is intended to short-circuit the continuation, and there are no conditionals, so it should always take the parameter, and (in either parlance) "make it a left value", and exit the scope.
So what is the type parameter B intended to be used for? It determines the return type of shift, but shift will not return. Given no more context, B can not be inferred, leading to this kind of code:
val res = either {
val intermediate = mayReturnNull()
if (intermediate == null) {
shift<Nothing>(IntermediateWasNull())
}
process(intermediate)
}
Note the <Nothing> (and ignore the contrived example, the main point is that shifts return type can not be inferred – the actual type parameter does not even matter).
I could wrap shift like this:
suspend fun <L> EffectScope<L>.fail(left: L): Nothing = shift(left)
But I feel like that is missing the point. Any explanations/hints would be greatly appreciated.
That is a great question!
This is more a matter of style, ideally we'd have both but they conflict so we cannot have both APIs available.
So shift always returns Nothing in its implementation, and so the B parameter is completely artificial.
This is something that is true for a lot of other things in Kotlin, such as object EmptyList : List<Nothing>. The Kotlin Std however exposes it as fun <A> emptyList(): List<A> = EmptyList.
For Arrow to stay consistent with APIs found in Kotlin Std, and to remain as Kotlin idiomatic as possible we also require a type argument just like emptyList. This has been up for discussion multiple times, and the Kotlin languages authors have stated that it was decided too explicitly require A for emptyList since that results in the best and most consistent ergonomics in Kotlin.
In the example you shared I would however recommend using ensureNotNull which will also smart-cast intermediate to non-null.
Arrow attempts to build the DSL so that you don't need to rely on shift in most cases, and you should prefer ensure and ensureNotNull when possible.
val res = either {
val intermediate = mayReturnNull()
ensureNotNull(intermediate) { IntermediateWasNull() }
process(intermediate) // <-- smart casted to non-null
}
I have a java method to create a constraintstream out of all TeamCalendar objects (planning entity with pinned=true) that do not follow a given Preference.
As a result I would like to have a constraint stream that denotes for each department how many of these discrepancies exist. This is the java code:
private BiConstraintStream<Department, Integer> violatedPreferredTeamDays(ConstraintFactory constraintFactory) {
return constraintFactory.from(TeamCalendar.class)
.ifNotExists(Preference.class,
Joiners.equal(TeamCalendar::getDate, Preference::getDate),
Joiners.equal(TeamCalendar::getDepartment, Preference::getDepartment))
.filter((tc) -> {
return true;
})
.groupBy(TeamCalendar::getDepartment, count())
.filter((dep, count) -> {
return true;
});
}
When running this in Debug mode, the first filter (tc -> {return true;}) is entered a total of 20 times, 5 times for each of the 4 departments.
However, after groupBy, the result set seems to be empty. The debugger does not break code execution in that filter, and the penalize function used on this BiConstraintStream does not penalize anything.
I tried some trivial alternatives:
.groupBy(tc -> tc.getDepartment(), count())
.groupBy(tc -> { return tc.getDepartment(); }, count() )
as well as alternatives for the count method:
.groupBy(TeamCalendar::getDepartment, sum(tc -> 1) )
.groupBy(TeamCalendar::getDepartment, sum(tc -> {return 1;})
But without any improvement.
Am I overlooking something here?
The underlying engine applies the first filter directly on the TeamCalendar and pre-empts the ifNotExists call. Therefore you can not use the filter to check whether or not the ifNotExists has triggered or not.
Constraint Streams are not imperative, and the execution of individual building blocks need not happen in sequence. Constraint Streams can not be inspected using the debugger, unless you understand how the underlying Drools engine evaluates the constraints, and adjust your expectations accordingly.
As to why the groupBy result is empty - probably because the Preference instance actually exists. If that is not the case, please file a JIRA, attach a minimal executable code reproducer, and we will look into it.
Using kotlin I can repeat an action in at least two ways:
val times = 5
// First option
for (i in 0 until times) {
print("Action $i")
}
// Second option
repeat(times) {
print("Action $it")
}
I'd like to know the purpose of repeat.
Should the traditional for loop be replaced with repeat function if possible?
Or are there special cases for this function?
Are there any advantages in repeat function?
EDIT
I've made some research about this question. As long as kotlin is open source project, I could download the sources and check git history.
I found that
1) repeat function is a replace for times function extension.
public inline fun Int.times(body : () -> Unit)
2) KT-7074. times function has become deprecated. But why?
It's just a matter of convenience (shortens the code). There are even more ways for example using an IntRange and forEach
(0..4).forEach {
println(it)
}
0 1 2 3 4
They all serve the same purpose, so the choice is yours.
You don't need to worry about performance either, since repeat and forEach are inline functions, which means the lambda code is copied over to the call site at compile time.
Next lines are all just my opinion:
there are no special cases when you should or shouldn't use repeat
function.
it has more concise syntax.
In places where you don't need to manipulate the loop counter or need to repeat only some simple action I would use that function.
It's all up to you to decide when and how to use it.
From Standard.kt:
/**
* Executes the given function [action] specified number of [times].
*
* A zero-based index of current iteration is passed as a parameter to [action].
*
* #sample samples.misc.ControlFlow.repeat
*/
#kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0 until times) {
action(index)
}
}
As you can see repeat(times) is actually for (index in 0 until times).
There is also a zero-based loop counter and it is: it.
Should the traditional for loop be replaced with repeat function if
possible?
I can't find any reason for that
Or are there special cases for this function?
None I can think of.
Are there any advantages in repeat function?
None I can think of, or maybe(?) just 1:
for educational purposes, I suppose it's easier to teach
that repeat(n) { } performs n iterations of the block of statements inside the curly brackets.
The Result docs give the following explanation for the .or_else() method:
fn or_else<F, O: FnOnce(E) -> Result<T, F>>(self, op: O) -> Result<T, F>
Calls op if the result is Err, otherwise returns the Ok value of self.
This function can be used for control flow based on result values.
Examples
fn sq(x: u32) -> Result<u32, u32> { Ok(x * x) }
fn err(x: u32) -> Result<u32, u32> { Err(x) }
assert_eq!(Ok(2).or_else(sq).or_else(sq), Ok(2));
assert_eq!(Ok(2).or_else(err).or_else(sq), Ok(2));
assert_eq!(Err(3).or_else(sq).or_else(err), Ok(9));
assert_eq!(Err(3).or_else(err).or_else(err), Err(3));
I think can parse the or_else type annotation with more whitespace:
fn or_else<F, // F being the return type of the Result?
O: FnOnce(E) -> Result<T, F> // the function to run instead if error
>
(self, op: O) // arguments to the `.or_else()` method
-> Result<T, F> // return type is again Result
Assuming I've got that right, does that mean .or_else() simply gives you a Result with the Error replaced with the return value of the op function?
I can understand returning a Result yet again, since all code downstream of possibly error-throwing code is "stained" with the possibility of further errors (and it's up to the caller to handle that). But why the doubled calls in the examples? There are a couple different permutations, but I'm not sure what they're trying to show (or if that doubled or_else() pattern is idiomatic).
The example may be a bit unfortunate, because it tries to show at the same time how does or_else work, and why would you use it.
To split it into two parts. First what does or_else actually do. If you get call it on Ok value, it passes the Ok value. If you call it on Err value, it executes the function. This example should be enough:
Ok(2).or_else(sq), Ok(2) // not called
Ok(2).or_else(err), Ok(2) // not called
Err(2).or_else(sq), Ok(4) // called, succeeds
Err(2).or_else(err), Err(3) // called, fails
Now, the why would you use it part. Imagine you're doing some operation which has many alternative approaches. For example you're trying to install some package on linux, but don't care which package manager is available - you're just going to brute-force it. With all the functions returning Result<...> you could do this:
install_with_apt().
or_else(install_with_yum).
or_else(install_with_pacman).
or_else(install_with_dnf).
or_else...
You'll know that if you got back Ok, at least one of those succeeded and if you get back Err all of them failed. The doubled .or_else() usage in the example if likely just trying to show you can easily chain this call.