Search code examples
scalamonadsscala-cats

Cannot resolve symbol flatMap on Monad constrainted type


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:

  • Scala 2.13.5
  • SBT 1.9.0
  • cats-core 2.5.0
  • cats-effects 2.4.1

Solution

  • This is almost certainly due to missing the import that provides the method.

    Add import cats.syntax.all._