scalafor-comprehensionhttp4spureconfigtagless-final

Why is Either expected in the following for comprehension?


I am playing with tagless final in scala. I use pureconfig to load the configuration and then use the configuration values to set the server port and host. Snippet

  def create[F[_]: Async] =
    for {
      config <- ConfigSource.default.at("shopkart").load[AppConfig]
      httpApp = EndpointApp.make[F]
      server <- BlazeServerBuilder[F]
        .bindHttp(port = config.http.port, host = config.http.host)
        .withHttpApp(httpApp)
        .resource
    } yield server

The compilation error is ambiguous to me. This is the compilation error.

type mismatch;
[error]  found   : cats.effect.kernel.Resource[F,Unit]
[error]  required: scala.util.Either[?,?]
[error]       server <- BlazeServerBuilder[F]
[error]              ^
[error] one error found

I understand that the ConfigSource.default.at("shopkart").load[AppConfig] returns Either[ConfigReaderFailures, AppConfig]. But within the context of for-comprehension, it is an instance of AppConfig. So, why in the following line where BlazeServerbuilder an Either is expected ?

My understanding is with in the context of for-comprehension, these are two different instances. Also, I came across a similar example in scala pet store https://github.com/pauljamescleary/scala-pet-store/blob/master/src/main/scala/io/github/pauljamescleary/petstore/Server.scala#L28

How to de-sugar for to understand this error better?


Solution

  • The code below that you would have got if you have used flatMap/map instead of for-comprehension.

    ConfigSource.default.at("shopkart").load[AppConfig] // Either[E, AppConfig]
      .flatMap { config => // in flatMap you should have the same type of Monad
        BlazeServerBuilder[F] // Resource[F, BlazeServerBilder[F]]
            .bindHttp(port = config.http.port, host = config.http.host)
            .withHttpApp(EndpointApp.make[F])
            .resource
      }
    

    The cause of your error that you can't use different types of a monad in one for-comprehension block. If you need that you should convert your monads to the same type. In your case the easiest way is converting your Either to Resource[F, AppConfig]. But you have to consider using F that can understand an error type of Either, like MonadError to handle error from Either and convert it to F. After you can use Resource.eval that expects F. I see that you use Async, so you could use Async[F].fromEither(config) for that.

      def create[F[_]: Async] =
        for {
          config <- Resource.eval(
             Async[F].fromEither(ConfigSource.default.at("shopkart").load[AppConfig])
          )
          httpApp = EndpointApp.make[F]
          server <- BlazeServerBuilder[F]
            .bindHttp(port = config.http.port, host = config.http.host)
            .withHttpApp(httpApp)
            .resource
        } yield server