Is there a more idiomatic way to write the following?
foo?.let{
if(!foo.isBlank()) {
bar?.let {
if(!bar.isBlank()) {
println("foo and bar both valid strings")
}
}
}
}
basically this the idea is that both strings should be nonNull and nonEmpty and I was wondering if there is a more Kotlin way than doing if(foo.isNullOrEmpty && !bar.isNullOrEmpty)
Use this
fun <T, R, S> biLet(lhs: T, rhs: R, block: (T, R) -> S): S? = if (lhs != null && rhs != null) block(lhs, rhs) else null
Use as
biLet(foo, bar) { safeFoo, safeBar ->
}
Edit: variant for strings
fun <T: CharSequence?, S> biLet(lhs: T, rhs: T, block: (T, T) -> S): S? =
if (lhs.isNotNullOrBlank() && rhs.isNotNullOrBlank()) block(lhs, rhs) else null
You can use sequenceOf and none:
if (sequenceOf(foo, bar).none { it.isNullOrBlank() }) {
println("foo and bar both valid strings")
}
Declare somewhere an extension function using lambdas like:
inline fun String.ifNotEmpty(bar: String, function: () -> Unit) {
if (this.isNotEmpty() && bar.isNotEmpty()) {
function.invoke()
}
}
And use it as:
val foo = "foo-value"
val bar = "bar-value"
foo.ifNotEmpty(bar) {
println("foo and bar both valid strings")
}
Improving #Francesc answer, I created a nLet version
fun <S> nLet(vararg ts: Any?, block: (Array<out Any?>) -> S): S? =
if (ts.none { when (it) { is String -> it.isNullOrEmpty() else -> it == null } }) block(ts) else null
You can use it like that
nLet (1, 2 , 3, "a", "B", true) { ts ->
ts.forEach { println(it) }
}
This is what I use:
fun <P1, P2, R> nLet(p1: P1?, p2: P2?, block: (P1, P2) -> R?): R? =
p1?.let { p2?.let { block(p1, p2) } }
Usage:
nLet(foo, bar) { f, b -> doStuff(f, b) }
Add more nLet functions with more P's if more arguments are needed.
You can also use this for an arbitary number of arguments:
fun <P, R> nLet(vararg ts: P?, block: (Array<out P?>) -> R): R? =
ts.takeIf { it.none { it == null } }?.let { block(it) }
Usage:
nLet(foo, bar, dog) { (f, b, d) -> doStuff(f, b, d) }
This works, but f, b and d will have nullable types even though they cannot be null.
(There might be a clever way to solve that...)
Related
Suppose I have three functions foo, bar, baz, all of which return nullable types.
fun foo(): Int? = 1
fun bar(): Int? = 2
fun baz(): Int? = 3
I want to call them, and if all them returns non-null, I want to compute a value from their return values.
I could do this with statements, like this:
val x = foo()
val y = bar()
val z = baz()
val result = if (x != null && y != null && z != null) x + y + z else null
However, I don't like the fact that I have to declare 3 extra variables that I can still access afterwards. By having 3 extra statements like this, it also means that I cannot use expression-bodied functions, if I were writing a function that returns result.
If I use lets instead:
val result = foo()?.let { x ->
bar()?.let { y ->
baz()?.let { z ->
x + y + z
}
}
}
This creates a deep nesting. If it were only one function, this would have been fine, but with 3 functions or more, this makes my intention of "call these three functions, if they are all non null, add them together" rather unclear.
How can I write this in a way that clearly conveys my intention, but also making it a single expression?
If they are of different types, I think you need to write your own helper functions like these (different overloads needed for different numbers of parameters, because there's no other way for the compiler to know the types of the arguments):
inline fun <T : Any, U : Any, R> ifAllNotNull(t: T?, u: U?, block: (t: T, u: U) -> R): R? {
return when {
t != null && u != null -> block(t, u)
else -> null
}
}
inline fun <T : Any, U : Any, V : Any, R> ifAllNotNull(t: T?, u: U?, v: V?, block: (t: T, u: U, v: V) -> R): R? {
return when {
t != null && u != null && v != null -> block(t, u, v)
else -> null
}
}
val result = ifAllNotNull(foo(), bar(), baz()) { x, y, z -> x + y + z }
Note that all three parameters will be evaluated before any are checked for null.
Or if you want to do what you described (hiding the three variables after the result calculation) using just standard library functions, you can use run to limit the scope of the temporary variables:
val result = run {
val x = foo()
val y = bar()
val z = baz()
if (x != null && y != null && z != null) x + y + z else null
}
This would also give you the opportunity to short-circuit if you like:
val result = run {
val x = foo() ?: return#run null
val y = bar() ?: return#run null
val z = baz() ?: return#run null
x + y + z
}
You could filter out all null-values and only apply an operation on the list, if it did not shrink in size, e.g.:
fun sumIfNoneNull(values: List<Int?>): Int? = values
.filterNotNull()
.takeIf { it.size == values.size }
?.sum()
One may generalize this further, e.g.:
fun <T, R> List<T>.foldIfNoneNull(
initial: R,
operation: (acc: R, T) -> R
): R? = this
.filterNotNull()
.takeIf { nonNullList -> nonNullList.size == this.size }
?.fold(initial, operation)
You may use this like any other fold, e.g.:
listOf(foo(), bar(), baz()).foldIfNoneNull(0) { acc, cur -> acc + cur }
val result = listOf(foo(), bar(), baz())
.reduce { acc, i ->
when {
acc == null || i == null -> null
else -> acc + i
}
}
Or as function:
fun <T> apply(operation: (T, T) -> T, vararg values: T?): T? {
return values
.reduce { acc, i ->
when {
acc == null || i == null -> null
else -> operation(acc, i)
}
}
}
val result = apply({ x, y -> x + y }, foo(), bar(), baz())
There is zip function to zip two Flows. Is there something to zip three (or more) Flows together?
If not, can you help me to implement extension function for it? Something like:
flow.zip(flow2, flow3) { a, b, c ->
}
I am new to Flows but this seems to be working for me.
// This will hold all 3 values
data class Foo(val i: Int, val j: Int, val k: Int)
val flow1 = (1..10).asFlow()
val flow2 = (11..20).asFlow()
val flow3 = (21..30).asFlow()
val combinedFlow = flow1.zip(flow2) {i, j ->
Pair(i, j)
}.zip(flow3) {pair, k ->
Foo(pair.first, pair.second, k)
}
Then you would collect them and get the values out like this:
viewModelScope.launch {
repo.combinedFlow.collect {foo ->
System.out.println(foo.i)
System.out.println(foo.j)
System.out.println(foo.k)
}
}
You can check the zip operator implementation and try to copy/emulate how it works adapting it to your needs.
Test it and make all the changes you need
fun <T1, T2, T3, R> Flow<T1>.zip(flow2: Flow<T2>, flow3: Flow<T3>, transform: suspend (T1, T2, T3) -> R): Flow<R> = channelFlow {
val first: ReceiveChannel<T1> = produce {
this#zip.collect {
channel.send(it)
}
}
val second: ReceiveChannel<T2> = produce {
flow2.collect {
channel.send(it)
}
}
val third: ReceiveChannel<T3> = produce {
flow3.collect {
channel.send(it)
}
}
(second as SendChannel<*>).invokeOnClose {
if (!first.isClosedForReceive) first.cancel(MyFlowException())
if (!third.isClosedForReceive) third.cancel(MyFlowException())
}
(third as SendChannel<*>).invokeOnClose {
if (!first.isClosedForReceive) first.cancel(MyFlowException())
if (!second.isClosedForReceive) second.cancel(MyFlowException())
}
val otherIterator = second.iterator()
val anotherIterator = third.iterator()
try {
first.consumeEach { value ->
if (!otherIterator.hasNext() || !anotherIterator.hasNext()) {
return#consumeEach
}
send(transform(value, otherIterator.next(), anotherIterator.next()))
}
} catch (e: MyFlowException) {
// complete
} finally {
if (!second.isClosedForReceive) second.cancel(MyFlowException())
if (!third.isClosedForReceive) third.cancel(MyFlowException())
}
}
class MyFlowException: CancellationException()
Usage:
flow1.zip(flow2, flow3) { a, b, c ->
//Do your work
}...
I have not tested this, but you can give it a try. There's a lot of underlying code for zip, so to exploit that I'm zipping the first two Flows into a Flow of Pairs, and then zip the Flow of Pairs to the third Flow. But the lambda passed to this function gets the first two already separated so it doesn't have to know about the intermediate Pair step.
fun <T1, T2, T3, R> zip(
first: Flow<T1>,
second: Flow<T2>,
third: Flow<T3>,
transform: suspend (T1, T2, T3) -> R
): Flow<R> =
first.zip(second) { a, b -> a to b }
.zip(third) { (a, b), c ->
transform(a, b, c)
}
Usage like this:
zip(flow1, flow2, flow3) { a, b, c ->
Triple(a, b, c)
}
And here's an untested version for an arbitrary number of flows, but they have to be of the same type:
fun <T, R> zip(
vararg flows: Flow<T>,
transform: suspend (List<T>) -> R
): Flow<R> = when(flows.size) {
0 -> error("No flows")
1 -> flows[0].map{ transform(listOf(it)) }
2 -> flows[0].zip(flows[1]) { a, b -> transform(listOf(a, b)) }
else -> {
var accFlow: Flow<List<T>> = flows[0].zip(flows[1]) { a, b -> listOf(a, b) }
for (i in 2 until flows.size) {
accFlow = accFlow.zip(flows[i]) { list, it ->
list + it
}
}
accFlow.map(transform)
}
}
Flow has combine function
fun <T1, T2, R> Flow<T1>.combine(
flow: Flow<T2>,
transform: suspend (a: T1, b: T2) -> R
): Flow<R>
It returns a Flow whose values are generated with transform function by combining the most recently emitted values by each flow.
Source
Example from srouces:
val flow = flowOf(1, 2).onEach { delay(10) }
val flow2 = flowOf("a", "b", "c").onEach { delay(15) }
combine(flow, flow2) { i, s -> i.toString() + s }.collect {
println(it) // Will print "1a 2a 2b 2c"
}
I'd like to make a function that takes a variable number of arguments of different types, and a closure, and call the closure with the same number of arguments, each corresponding to a type in the original argument list:
fun <A, B, ...>mergeWhenValid(
arg1: Either<Problem, A>,
arg2: Either<Problem, B>,
...,
closure: (A, B, ...) -> T
): Either<Problem, T> {
// do stuff and call closure(a, b, ...)
}
How might I accomplish this?
If your mergeWhenValid just returns closure result if all eithers are right and firstProblem.left() otherwise, you should use Either.fx<Problem, T> instead of your function. Example:
Either.fx<Problem, String> { "${eitherInt.bind()} ${eitherDouble.bind()} ${eitherFloat.bind()}" }
If your logic is more complex and you need somehow handle all eithers, you can do it either by creating special merging DSL:
fun <R> mergeWhenValid(block: MergeWhenValidScope.() -> R): R = MergeWhenValidScope().block()
class EitherProblem<out T>(internal val either: Either<Problem, T>)
class MergeWhenValidScope {
private val eithers = mutableListOf<Either<Problem, *>>()
operator fun <T> Either<Problem, T>.component1(): EitherProblem<T> {
eithers += this
return EitherProblem(this)
}
private fun doStuff(): Option<Problem> {
// you can use `eithers` here and choose one of their problems or create a new one
// if you return None, it will assume that all `eithers` are right,
// otherwise, problem will be wrapped in Either and returned
return eithers.asSequence().mapNotNull { it.swap().getOrElse { null } }.firstOption()
}
fun <R> combine(block: CombinerScope.() -> R): Either<Problem, R> =
doStuff().map { it.left() }.getOrElse { CombinerScope.block().right() }
object CombinerScope {
operator fun <T> EitherProblem<T>.invoke() = either.getOrHandle {
error("Unexpected problem $it")
}
}
}
Use case:
mergeWhenValid {
val (int) = eitherInt
val (double) = eitherDouble
val (float) = eitherFloat
combine { "${int()} ${double()} ${float()}" }
}
Or by pipelining functions which add all your eithers to some object:
fun <T> mergeWhenValid() = MergeWhenValidInit<T>()
class MergeWhenValidInit<T> {
operator fun <A> invoke(either: Either<Problem, A>): MergeWhenValid<A, T, T> =
MergeWhenValid(either, listOf(either)) { it }
}
class MergeWhenValid<A, B, C>(
private val either: Either<Problem, A>,
private val eithers: List<Either<Problem, *>>,
private val previous: (B) -> C // is allowed to be called only if all `eithers` are right
) {
private fun doStuff(): Option<Problem> {
// you can use `eithers` here and choose one of their problems or create a new one
// if you return None, it will assume that all `eithers` are right,
// otherwise, problem will be wrapped in Either and returned
return eithers.asSequence().mapNotNull { it.swap().getOrElse { null } }.firstOption()
}
operator fun invoke(block: (A) -> B): Either<Problem, C> =
doStuff().map { it.left() }.getOrElse { requireC(block).right() }
operator fun <D> invoke(either: Either<Problem, D>): MergeWhenValid<D, (A) -> B, C> =
MergeWhenValid(either, eithers + either) { next -> requireC(next) }
private fun requireC(next: (A) -> B): C = previous(next(either.getOrHandle {
error("Unexpected problem $it")
}))
}
Use case:
mergeWhenValid<String>()(eitherInt)(eitherDouble)(eitherFloat)() { float ->
{ double -> { int -> "$int $double $float" } }
}
Note: the last approach reverses the order of arguments and also forces you to write { c -> { b -> { a -> ... } } } instead of { c, b, a -> ... }.
So in RxJava, we could simply do:
Observable.zip(someObservable, anotherObservable, BiFunction { a, b -> //do something }.subscribe { // do something }
How do we do the same thing with Kotlin Coroutine Channels?
not ideal solution but it seems to work
#ExperimentalCoroutinesApi
private fun <T, R> CoroutineScope.zipChannels(
channel1: ReceiveChannel<T>,
channel2: ReceiveChannel<T>,
zip: (T, T) -> R
): ReceiveChannel<R> = produce {
val iterator1 = channel1.iterator()
val iterator2 = channel2.iterator()
while (iterator1.hasNext() && iterator2.hasNext()) {
val value1 = iterator1.next()
val value2 = iterator2.next()
send(zip(value1, value2))
}
channel1.cancel()
channel2.cancel()
close()
}
Update
Also, there is a deprecated operator zip
I need to implement the following .combineLatest() extension function on a ReceiveChannel
suspend fun <A, B, R> ReceiveChannel<A>.combineLatest(
otherSource: ReceiveChannel<B>,
context: CoroutineContext = Unconfined,
combineFunction: suspend (A, B) -> R
): ReceiveChannel<R> = produce(context) {
// ?
}
I would like it to function just like RxJava's combineLatest().
How can I do this?
EDIT: So far I have this, but it's not working. The sourceB.consumeEach{ } block is never excecuted.
suspend fun <A, B, R> ReceiveChannel<A>.combineLatest(
otherSource: ReceiveChannel<B>,
context: CoroutineContext = Unconfined,
combineFunction: suspend (A, B) -> R
): ReceiveChannel<R> = produce(context) {
val sourceA: ReceiveChannel<A> = this#combineLatest
val sourceB: ReceiveChannel<B> = otherSource
var latestA: A? = null
var latestB: B? = null
sourceA.consumeEach { a ->
latestA = a
if (latestA != null && latestB != null) {
send(combineFunction(latestA!!, latestB!!))
}
}
sourceB.consumeEach { b ->
latestB = b
if (latestA != null && latestB != null) {
send(combineFunction(latestA!!, latestB!!))
}
}
}
I also want to make sure that when the ReceiveChannel<R> returned by this function is closed (unsubscribed from), I want to make sure that the parent channels are closed properly.
This did the trick! I am still confused why I can nest one .consumeEach{ } inside another .consumeEach { } - it seems unintuitive.
suspend fun <A, B, R> ReceiveChannel<A>.combineLatest(
otherSource: ReceiveChannel<B>,
context: CoroutineContext = Unconfined,
combineFunction: suspend (A, B) -> R
): ReceiveChannel<R> = produce(context) {
val sourceA: ReceiveChannel<A> = this#combineLatest
val sourceB: ReceiveChannel<B> = otherSource
val latestA = AtomicReference<A>()
val latestB = AtomicReference<B>()
var aInitialized = false
var bInitialized = false
sourceA.consumeEach { a ->
latestA.set(a)
aInitialized = true
if (aInitialized && bInitialized) {
send(combineFunction(latestA.get(), latestB.get()))
}
launch(coroutineContext) {
sourceB.consumeEach { b ->
latestB.set(b)
bInitialized = true
if (aInitialized && bInitialized) {
send(combineFunction(latestA.get(), latestB.get()))
}
}
}
}
}
I understand this is an old question but here is a suggestion:
I would recommned using .zip() instead of nesting .consumeEach. Checkout the documentation here.
Possible solution sourceA.zip(sourceB).consumeEach{} which produces an item of type Pair.