Search code examples
scalascala-cats

Why does the code with extension method compile?


I have two functions:

  override def build_url(dbType: DbDriverType, dbAddr: String, dbName: String): F[DbAddr] = dbType match {
    case PostgresSql =>
      Applicative[F].pure(DbAddr("jdbc:postgresql://" |+| dbAddr |+| "/" |+| dbName))
    case _ => DbDriverError.raiseError[F, DbAddr]
  }

  override def get_db_driver(dbType: DbDriverType): F[DbDriver] = dbType match {
    case PostgresSql =>
      Applicative[F].pure(DbDriver("org.postgresql.Driver"))
    case _ => Applicative.raiseError[F, DbDriver](DbDriverError)
  }

The first compiles and the second not. The compiler complains:

[error]  found   : io.databaker.db.DbDriverError.type
[error]  required: cats.ApplicativeError[F, _ >: cats.Applicative.type]
[error]     case _ => Applicative.raiseError[F, DbDriver](DbDriverError)

On the first function, I use on DbDriverError.raiseError[F, DbAddr] the extension method and on the second not. For me, it should be the same.

What is the difference?


Solution

  • On the second case Applicative.raiseError[F, DbDriver](DbDriverError) you are using extension method on Applicative compaion object. So this

    Applicative.raiseError[F, DbDriver](DbDriverError)
    

    is virtually this

    new ApplicativeErrorIdOps[F, Applicative.type](Applicative).raiseError[DbDriver](DbDriverError)(implicitly[ApplicativeError[F, Applicative.type]])
    

    Obviously there is no ApplicativeError[F, Applicative.type] instance, if your code is similar to @Luis Miguel Mejía Suárez's scastei, then there is ApplicativeError[F, Throwable] in the form of Sync[F].

    On the first example it works because DbDriverError.raiseError[F, DbAddr] is the extension method which performs

    new ApplicativeErrorIdOps[F, DbDriverError.type](DbDriverError).raiseError[DbAddr](DbDriverError)(implicitly[ApplicativeError[F, DbDriverError.type]])
    

    assuming that object DbDriverError extends Exception (or some other Throwable) it is equal to directly calling

    implicitly[ApplicativeError[F, Throwable]].raiseError[DbAddr](DbDriverError)
    

    where this implicit could also take MonadError[F, Throwable], Sync[F] or any other implementeation of ApplicativeError[F, Throwable]. Since companions have summon method you could just write:

    ApplicativeError[F, Throwable].raiseError[DbDriver](DbDriverError)
    // not the same as
    // Applicative.raiseError[F, DbDriver](DbDriverError)
    // !!!
    // See where are square brackets and type parameters!
    // And the class doesn't even match!
    

    TLDR:

    Applicative[F].sth (calling Applicative.apply[F[_]](implicit a: Applicative[F]): Applicative[F] is not the same as Applicative.sth (calling method/extension method on Applicative companion object, especially if we need a method from ApplicativeError that isn't defined on Applicative).