Type inference with functional builders - kotlin

I'm using Kotlin KBuilders with some protobuffs and have run into a situation that is confusing me.
To start off, I have a function that takes a file name and list of serialized JSON and deserialized that JSON to a protobuff.
fun parseFileData(fileName: String, lines: List<String>): Data.Builder.() -> Unit = when (fileName) {
SOME_FILE_NAME -> deserializeLinesToModel(lines, DataModel::class.java)
.let {
return {
dataMeasurement = buildDataMeasurement {
property1 = it.reduce { acc, n -> acc + n }
measurementMsec = it.map { it.measurementMsec }
}
}
}
else -> throw UnsupportedOperationException()
The first thing I didn't understand was why I needed the return inside the let block. But it worked so I moved on.
I later decided to refactor some stuff to make code elsewhere simpler and ended up with something like this:
fun parseFileData(fileName: String, factory: DataFactory): Sequence<Data.Builder.() -> Unit> = when (fileName) {
SOME_FILE_NAME -> factory.getSomeFileSequence() // returns Sequence<Model>
.batch(1000) // process data in batches of 1000 to reduce memory footprint and payload size
.map { return {
dataMeasurement = buildDataMeasurement {
property1 = it.reduce { acc, n -> acc + n }
measurementMsec = it.map { it.measurementMsec }
}
}
else -> throw UnsupportedOperationException()
So basically, instead of processes each batch as a list, I read the sequence from the factory, batch it into a sequence of lists and try to map each list to a Data.Builder.() -> Unit. However, this time I get return is not allowed here. I've tried multiple variations, with and without return and map and let and whatnot. The closest I've gotten to is a return type of Sequence<() -> Unit> which fails type inference.
Can anyone explain what's going on here? And why this type cannot be inferred?

return in the map lambda is a non-local return. It tries to return from the closest fun function, which happens to be parseFileData.
Non-local returns are only allowed from inline functions, whose lambda parameters are inlined at the call site, and the map extension for Sequence is not an inline function.
If you want to return a value from the lambda itself, use qualified return return#map ..., or omit it completely: then the last expression in the block will be returned as the result.

Related

Using flatMap with a Completable

I am trying to make an API call multiple times, passing in a different parameter. When no more data is returned, the rx stream should terminate. After each call, the data is stored in my local repository. Here is what I have:
val startPositions = BehaviorSubject.createDefault(0)
startPositions.flatMap { startPos -> App.context.repository.getConnections(startPos) }
.flatMap { connections -> App.context.repository.storeConnections(connections) }
.doOnNext { startPos -> startPositions.onNext(startPos + 1) }
.subscribe({ startPos -> println("Index $startPos") })
Here is the api method to handle downloading the data:
override fun getConnections(startPos: Int): Observable<List<Connection>> {
return myAPI.getConnections(startPos)
}
And this is the api method for storing the data:
override fun storeConnections(connections: List<Connection>): Completable =
Completable.fromAction {
appDao.storeConnections(connections.map {
mapper.toDb(it)
})
}
The compile error I get is:
Type mismatch: inferred type is (List) -> Completable but ((List) -> ObservableSource!)! was expected
If possible, I don't want to change the return types of my api calls. I'm also not certain about using a flatMap. The data returned from downloading is a list and I want the list to remain as a list in the stream. I don't want to emit individual list items.
In order to fix the type mismatch error, use operator flatMapCompletable instead of flatMap:
.flatMapCompletable { connections -> App.context.repository.storeConnections(connections) }
Then you will have to use doOnComplete instead of doOnNext:
.doOnComplete { startPos -> startPositions.onNext(startPos + 1) }

Kotlin arrow transform a List of failures to a Failure of a list

How can I transform the following:
List<Try<String>>
to:
Try<List<String>>
Using kotlin and the functional library arrow (0.8.2). I would like to wrap it in a custom exception. It does not matter which one of the 'String' failed.
Update:
As the below answers will suffice, but I find it really hard to read. So, I implemented the following:
Create the following function:
fun getFailedStrings(result: List<Try<String>>): List<Failure> {
return result.fold(
initial = listOf(),
operation = { accumulator, nextUpdate ->
nextUpdate.fold(
ifSuccess = { accumulator },
ifFailure = { accumulator + Failure(it) }
)
})
}
Then use the result of the function:
return if (failedStrings.isNotEmpty()) {
failedStrings.first() // or whatever fits your usecase
} else {
// strings is the initial result of List<Try<String>>
Success(strings.mapNotNull { it.orNull() })
}
If we don't care about keeping the original exceptions we could do something like this with traverse:
val traversedTries = tries.traverse(Try.applicative(), ::identity)
This will return an instance of type Try<ListK<String>> with either all the strings or the first exception it finds.
ListK extends from List but we can optionally cast it by adding .map { it as List<String> } in the end if we need it to be Try<List<String>>
Alternatively, if we want to split the successes and failures we can create the following function:
fun <A> List<Try<A>>.splitSuccessFailure() : Tuple2<List<A>, List<Throwable>> =
fold(emptyList<A>() toT emptyList<Throwable>()) { (successes, failures), it ->
it.fold({ successes toT (failures + it) }, { (successes + it) toT failures })
}
Then, when we want to use it we can do the following:
val (successes, failures) = invalidTries.splitSuccessFailure()
Giving us two lists with the success values and failures respectively.
this seems to work:
fun convert(input: List<Try<String>>): Try<List<String>> =
input.fold(Try.just(emptyList())) { acc, i ->
acc.flatMap { list ->
i.flatMap {
Try.just(list + it)
}
}
}

Simplifying return from multiple nested blocks in Kotlin

The code in question is as:
fun get(context: Context, s: String): MyObjectDb? {
return context.database.use {
return#use select(MyObjectDb.TABLE_NAME, *MyObjectDb.PROJECTION)
.whereArgs("${MyObjectDb.COLUMN_S} = {s}", "s" to s)
.exec {
return#exec getOne(MyObjectDb::fromCursor)
}
}
}
When I check this for code style (sonar with Kotlin plugin that uses detekt) I get a warning that I should "Restrict the number of return statements in methods."
Is there a way to only return in return#exec or to write the code in more Kotlinized way - without so many returns.
You can omit return when a lambda only contain a single expression. Since your function also only contains a single expression, you can write the function body after a = to omit the return here too. You can therefore shorten your code to this:
fun get(context: Context, s: String): MyObjectDb? = context.database.use {
select(MyObjectDb.TABLE_NAME, *MyObjectDb.PROJECTION)
.whereArgs("${MyObjectDb.COLUMN_S} = {s}", "s" to s)
.exec { getOne(MyObjectDb::fromCursor) }
}

How to use Kotlin fold function to convert an array into a map?

I am trying to convert an Array via fold into an indexed Map. Somehow IntelliJ flags that when I return the accumulator that it expects Unit. When I remove the return it complains that I require the datatype I originally wanted to return.
The code is as follows (Item is just a data class)
constructor(vararg items: Item){
val itemMap = items.fold(mutableMapOf<Int, MutableList<Item>>(), { acc, item ->
if (acc.containsKey(item.state)) {
acc[item.state]?.add(item)
} else {
acc.put(item.state, mutableListOf(item))
}
return acc
})
}
Its a bit late here so I probably miss something very obvious. Any help would be very appreciated.
Thanks
Use the qualified return#fold operator instead of return. In Kotlin, return without a qualifier means 'return from the innermost fun (ignoring lambdas)'.
val itemMap = items.fold(mutableMapOf<Int, MutableList<Item>>(), { acc, item ->
if (acc.containsKey(item.state)) {
acc[item.state]?.add(item)
} else {
acc.put(item.state, mutableListOf(item))
}
return#fold acc
})
See Whats does “return#” mean?, Return at Labels in the language reference.
Or just use the result expression, omitting return:
val itemMap = items.fold(mutableMapOf<Int, MutableList<Item>>(), { acc, item ->
if (acc.containsKey(item.state)) {
acc[item.state]?.add(item)
} else {
acc.put(item.state, mutableListOf(item))
}
acc
})
Basically, this kind of fold is implemented in the standard library: see .groupBy { ... }.

Mapping unless exception

I have a list of Strings representing serialized data that I want to map to a list of objects. I use the following code:
strings.map { gson.fromJson(it, Model::class.java) }
// .doOtherStuff
However, sometimes there is a parsing error and the stream just stop, I would like to able to recover the list up until the point of failure. For example, if the error occurs at item 7, I would like doOtherStuff to get the 6 items that were successfully processed.
What's the most idiomatic way to do this? I can filter the list to see if the parsing will succeed but that's an expensive operation being doing twice.
You can treat an exception as null and then filter the nulls.
val models = modelsInJson.mapNotNull { json ->
try {
gson.fromJson(json, Model::class.java)
} catch (ex: WhateverException) {
// TODO: logging here?
null
}
}
Replace WhateverException with the correct one for the type of errors you want to handle, other errors can still stop the processing.
What you are looking for seems to be a combination of map and takeWhile. You can always roll your own. e.g. The following is adapted from the sources of mapNotNull and takeWhile:
inline fun <T, R : Any> Iterable<T>.mapWhileNotNull(transform: (T) -> R?): List<R> {
return mapWhileNotNullTo(ArrayList<R>(), transform)
}
inline fun <T, R : Any, C : MutableCollection<in R>> Iterable<T>.mapWhileNotNullTo(destination: C, transform: (T) -> R?): C {
for (element in this) {
val transformed = transform(element)
if (transformed == null) {
break
} else {
destination.add(transformed)
}
}
return destination
}
Now you can map an iterable up to a transform invocation that results in null and, like Jayson Minard's example, you can map whatever exception you need to null:
strings.mapWhileNotNull {
try {
gson.fromJson(it, Model::class.java)
} catch (e: JsonSyntaxException) {
null
}
}
// .doOtherStuff