Why is Either expected in the following for comprehension?

104 views Asked by At

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?

1

There are 1 answers

0
Stanislav Kovalenko On BEST ANSWER

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