Search code examples
scalascala-catshttp4scats-effect

How use guave cache loader in F polymorphic code


I have a service, that returns joke from official example:

  final case class JokeError(e: Throwable) extends RuntimeException

  def impl[F[_] : Sync](C: Client[F]): Jokes[F] = new Jokes[F] {
    val dsl = new Http4sClientDsl[F] {}
    import dsl._
    def get: F[Jokes.Joke] = {
      C.expect[Joke](GET(uri"https://icanhazdadjoke.com/"))
        .adaptError { case t => JokeError(t) }
    }
  }

But I want cache first requested joke (just by constant key, this doesn't matter) with guava cache:

object Jokes {
  def apply[F[_]](implicit ev: Jokes[F]): Jokes[F] = ev

  final case class Joke(joke: String) extends AnyRef
  object Joke {
    implicit val jokeDecoder: Decoder[Joke] = deriveDecoder[Joke]
    implicit def jokeEntityDecoder[F[_]: Sync]: EntityDecoder[F, Joke] =
      jsonOf
    implicit val jokeEncoder: Encoder[Joke] = deriveEncoder[Joke]
    implicit def jokeEntityEncoder[F[_]: Applicative]: EntityEncoder[F, Joke] =
      jsonEncoderOf
  }

  final case class JokeError(e: Throwable) extends RuntimeException

  def impl[F[_]: Sync](C: Client[F]): Jokes[F] = new Jokes[F]{

    val cacheLoader : CacheLoader[String, Joke] = new CacheLoader[String, Joke] {
      override def load(key: String): Joke = {
        import dsl._
        val joke: F[Joke] = C.expect[Joke](GET(uri"https://icanhazdadjoke.com/"))
          .adaptError{ case t => JokeError(t)}
        //? F[Joke] => Joke
        null
      }
    }

    val cache = CacheBuilder.newBuilder().build(cacheLoader)

    val dsl = new Http4sClientDsl[F]{}

    def get: F[Jokes.Joke] = {
       //it's ok?
       cache.get("constant").pure[F]
    }
  }
}

As you can see, cacheLoader requires "materialized" value F[Joke] => Joke. And cache return pure value without F

How can I use this cache in F polymorpic code?


Solution

  • You're basically asking how to run code polymorphic in F, to do so you need an Effect constraint to your F.

    Also instead of using pure, you would need to use delay, since getting a value from the cache is a side effect.

    val cacheLoader : CacheLoader[String, Joke] = new CacheLoader[String, Joke] {
      override def load(key: String): Joke = {
        import dsl._
        val joke: F[Joke] = C.expect[Joke](GET(uri"https://icanhazdadjoke.com/"))
          .adaptError{ case t => JokeError(t)}
    
        // This is a side effect, but can't avoid it due to the way the API is designed
        joke.toIO.unsafeRunSync()
      }
    }
    
    val cache = CacheBuilder.newBuilder().build(cacheLoader)
    
    val dsl = new Http4sClientDsl[F]{}
    
    def get: F[Jokes.Joke] = {
       // This is okay :)
       Sync[F].delay(cache.get("constant"))
    }
    

    As an aside, if you want to use something that interoperates really well with http4s, I strongly recommend mules. Check it out here: https://github.com/ChristopherDavenport/mules