0
votes

I am trying to use the first time tagless final pattern in Scala and struggling a bit.

I have the following algebras definition:

trait DbSetting[F[_]] {
  def read(url: String, user: String, pw: String): F[DbParameter]
}

trait Environment[F[_]] {
  def get(v: String) : F[Option[String]]
}

and the interpreters implementation:

final case class DbParameter(url: String, user: String, pw: String)

sealed trait DbError extends NoStackTrace
case object DbSettingError extends DbError


sealed trait DbError extends NoStackTrace
case object DbSettingError extends DbError


// Environment implementation
object Environment {

  def apply[F[_]](implicit ev: Environment[F]): ev.type = ev

  def impl[F[_]: Sync]: Environment[F] = new Environment[F] {
    override def get(v: String): F[Option[String]] =
      Sync[F].delay(sys.env.get(v))
  }
}

// DbSetting implementation
class DbSystemEnvironment[F[_] : MonadError[*, Throwable]] private(env: Environment[F])
  extends DbSetting[F] {
  override def read(url: String, user: String, pw: String): F[DbParameter] = env.get(url)

}

What I am trying to do is, to compose Environment into DbSystemEnvironment. The problem here is, I can not get the value out of env.get(url) because, I do not know anything about F in DbSystemEnvironment, except it is a MonadError. How to get the value out of env.get(url)::F[Option[String]]?

In addition, if env.get(url) returns Nothing in read function, then it should return MonadError.

1
Why do you mean by "getting the value of env.get(url)"? You want to .map it, .handle errors or something else? Because with algebras you have defined for F (Sync/MonadError), you can lift value, map/flatMap it handle, mapN, etc, but not turn F[A] into A (that would require Comonad).Mateusz Kubuszok
Tangential, but I would look at ZIO (zio.dev) as an alternative to tagless final. I personally find it easier to deal with.Eric

1 Answers

1
votes

If I understand your question correctly you are asking how to extract the returned value from calling env.get(url) and then map it to a F[DbParameter].

Since you have MonadError for your F[_] you should be able to map the result pretty readily by doing something like this:

import cats.syntax.flatMap._
import cats.syntax.applicative._
import cats.syntax.applicativeError._

override def read(url: String, user: String, pw: String): F[DbParameter] = 
  env.get(url).flatMap {
    case Some(ev) => DbParameter(ev, user, pw).pure[F]
    case None     => (new Exception("No environment parameter found!")).raiseError[F, DbParameter]
  }