I've been playing with scala cats and ended up stuck with implicits and type constraints.
Assume the following definitions:
trait Client[F[_]] {
def fetchPossiblePairs(fetcher: Uri => F[List[String]]): F[List[Rate.Pair]]
def fetchQuotes(currencyPairs: List[Rate.Pair], fetcher: Uri => F[List[QuoteDTO]]): F[List[Rate]]
def convertRate(uri: Uri): F[List[QuoteDTO]]
def symbols(uri: Uri): F[List[String]]
}
object Quotes {
private def getCurrencyPairs[F[_]: Monad](client: Client[F],
existingRates: Map[Rate.Pair, Rate]): F[List[Rate.Pair]] =
if (existingRates.isEmpty) {
client.fetchPossiblePairs(client.symbols)
} else {
existingRates.keySet.toList.pure[F]
}
private def updateCache(existingRates: Map[Rate.Pair, Rate], newRates: List[Rate]): Map[Rate.Pair, Rate] =
newRates.foldRight(existingRates)((rate: Rate, acc: Map[Rate.Pair, Rate]) => acc.updated(rate.pair, rate))
}
The following implementation works, but looks ugly and not really up to the standards:
object Quotes {
private def refreshRatesCache[F[_]: Monad](client: Client[F],
existingRates: Map[Rate.Pair, Rate]): F[Map[Rate.Pair, Rate]] = {
val currencyPairs1 = getCurrencyPairs(client, existingRates)
Monad[F]
.flatMap(currencyPairs1)(currencyPairs => client.fetchQuotes(currencyPairs, uri => client.convertRate(uri)))
.map(quotes => updateCache(existingRates, quotes))
}
}
Making it more conventional, using the for
comprehension, IDE shows an error Cannot resolve symbol flatMap
:
object Quotes {
private def refreshRatesCache[F[_]: Monad](client: Client[F],
existingRates: Map[Rate.Pair, Rate]): F[Map[Rate.Pair, Rate]] =
for {
currencyPairs <- getCurrencyPairs(client, existingRates)
// ^
// |--- Cannot resolve symbol flatMap
//
rates <- client.fetchQuotes(currencyPairs, uri => client.convertRate(uri))
} yield updateCache(existingRates, rates)
}
And SBT shows a slightly different error:
value flatMap is not a member of type parameter F[List[Rate.Pair]]
My understanding is that the two implementations are the same, just using the different syntactic sugar.
getCurrencyPairs[F[_]: Monad](...)
returns a wrapped List
, F[List[Rate.Pair]]
, and given the type constraint on F[_]: Monad
I assume Scala should be able to infer F extends Monad
and find all the Monad
's methods (including flatMap
).
The entire for
comprehension works within the same context of F[_]
, hence seems it is not the type mismatch.
I tried to expand the implicits and for
comprehension, hence arriving at this ugly implementation:
Monad[F]
.flatMap
(getCurrencyPairs(client, existingRates))
(currencyPairs => client.fetchQuotes(currencyPairs, uri => client.convertRate(uri)))
.map(quotes => updateCache(existingRates, quotes))
It does not make a lot of sense to have (implicit m: Monad[F[_]])
, in my opinion (at least I could not come up with the correct usage for it).
I am not really proficient with Scala implicits, but seems like there is something wrong with my code. Where am I wrong?
The versions I'm using are:
2.13.5
1.9.0
2.5.0
2.4.1
This is almost certainly due to missing the import that provides the method.
Add import cats.syntax.all._