I have to perform high-level operations that are always the same: fetch something, and store it locally. The issue I am facing is that the "fetching" and the "storing" deal with different types depending on certain conditions. Given the proximity of the operations, it seemed like code waste to repeat the code for every type, so I tried making it generic... without much success.
Here is a code example that shows the issue I am facing:
suspend fun startFetching(dataPeriodicity: StockDataPeriodicity) {
val stock = stockRepository.insertStock(stockDetails)
.ifDuplicateGetExistingWith { stockRepository.getStockBy(stockDetails.symbol).trust() }
.trust()
val (fetcher, storer) =
if (dataPeriodicity.isIntraday)
stockDataClient::getIntradayPriceDataForStock to intradayTimeSeriesRepository::insertNonExisting
else
stockDataClient::getInterdayPriceDataForStock to interdayTimeSeriesRepository::insertNonExisting
while (true) {
fetchAndStore(stock, dataPeriodicity, fetcher, storer)
delay(fetchingInterval.inWholeMilliseconds)
}
}
private suspend inline fun <reified T, reified R : Comparable<R>> fetchAndStore(
stock: Stock,
periodicity: StockDataPeriodicity,
crossinline fetcher: suspend (symbol: String, periodicity: StockDataPeriodicity) -> T,
storer: (stock: Stock, values: T) -> Either<Throwable, List<Entity<R>>>
) = storer(stock, fetcher(stockDetails.symbol, periodicity))
Here is a screenshot with intellij type inferences and errors for context:
Here are the signatures for getInterdayPriceDataForStock and getIntradayPriceDataForStock
suspend fun getInterdayPriceDataForStock(
stockSymbol: String,
periodicity: StockDataPeriodicity
): Either<Throwable, StockPriceInterdayValues>
suspend fun getIntradayPriceDataForStock(
stockSymbol: String,
periodicity: StockDataPeriodicity
): Either<Throwable, StockPriceIntradayValues>
Here are the signatures for both insertNonExisting functions for the two different repositories
fun insertNonExisting(relatedStock: Stock, values: StockPriceIntradayValues): Either<Throwable, List<IntradayTimeSeries>>
fun insertNonExisting(relatedStock: Stock, values: StockPriceInterdayValues): Either<Throwable, List<InterdayTimeSeries>>
I was expecting that the compiler was able to, with the reified types, understand the types involved in runtime when the "fetchAndStore" call is reached.
It didn't, and don't really know how to untangle it from here.
Already tried to have a shared interface by the return of getIntradayPriceDataForStock and getInterdayPriceDataForStock, but it didn't work.
Also, the signature on the storer that looks like {type1 & type2} sounds fishy for me.
Bottom-line: I tried my best with my knowledge of generics but I guess I am missing something. Any help is appreciated.
CodePudding user response:
You should put the whole loop into the inline method, and call it earlier, when the types of the functions are still known.
private suspend inline fun <reified T, reified R : Comparable<R>> fetchAndStore(
stock: Stock,
periodicity: StockDataPeriodicity,
// note that you made a mistake here - the function type should return
// Either<Throwable, T>, not T
crossinline fetcher: suspend (symbol: String, periodicity: StockDataPeriodicity) -> Either<Throwable, T>,
storer: (stock: Stock, values: T) -> Either<Throwable, List<Entity<R>>>
) {
while (true) {
storer(stock, fetcher(stockDetails.symbol, periodicity).second)
delay(fetchingInterval.inWholeMilliseconds)
}
}
// in startFetching:
if (dataPeriodicity.isIntraday)
fetchAndStore(stock, dataPeriodicity, stockDataClient::getIntradayPriceDataForStock, intradayTimeSeriesRepository::insertNonExisting)
else
fetchAndStore(stock, dataPeriodicity, stockDataClient::getInterdayPriceDataForStock, interdayTimeSeriesRepository::insertNonExisting)
If you don't like passing stock and dataPeriodicity twice, you can make fetchAndStore a local function in startFetching and remove the parameters, but that means it can't be inline anymore.
Explanation
Reified types do not allow the compiler to "understand the types involved in runtime when the 'fetchAndStore' call is reached". Reified types allow generic types arguments to be known at runtime, by inlining them into the callsite. This all depends on the compiler knowing what type it is at compile-time, and at the line,
fetchAndStore(stock, dataPeriodicity, fetcher, storer)
the compiler doesn't know which branch of the if statement will be executed, so all it can work out at compile time are the following types:
fetcheris a suspending function taking aString,StockDataPeriodicityand returning anEither<Throwable, Any>. Notice that the compiler doesn't know second type parameter because there is nothing in common betweenStockPriceInterdayValuesandStockPriceIntradayValuesstoreris a function that takesStockand{ StockPriceIntradayValues & StockPriceInterdayValues }, and returnsEither<Throwable, List<LongEntity>>.{ StockPriceIntradayValues & StockPriceInterdayValues }means a type that is a subtype of both of those two types, which I don't think is even possible. This is because one of theinsertNonExistingtakes aStockPriceIntradayValues, and the other takes aStockPriceInterdayValues, which is an unrelated type. The overallstorercan take neither :(
As you can see, these are totally not the types that you want fetcher and storer to have, but these are the types that the compiler would use to call your generic method. And this is why it fails.

