I am trying to model a dependency using Kleisli. For instance, let's imagine I have the following business logic types:
import $ivy.`org.typelevel:cats-core_2.13:2.2.0`
import cats._
import cats.implicits._
trait Decoder[F[_]] {
def decode(s: String): F[String]
}
trait DatabaseAccess[F[_]] {
def getData(): F[String]
}
trait BusinessLogicService[F[_]] {
def getTheProcessedData(): F[String]
}
object BusinessLogicService {
def make[F[_]: Monad](
decoder: Decoder[F],
db: DatabaseAccess[F]
): BusinessLogicService[F] =
new BusinessLogicService[F] {
override def getTheProcessedData(): F[String] = for {
str <- db.getData()
decodedStr <- decoder.decode(str)
} yield decodedStr
}
}
And now I have the following implementations for Decode and DatabaseAccess:
import cats.data.Kleisli
trait DbSession {
def runQuery(): String
}
type ErrorOr[A] = Either[Throwable, A]
type DbSessionDependency[A] = Kleisli[ErrorOr, DbSession, A]
type NoDependencies[A] = Kleisli[ErrorOr, Any, A]
object PlainDecoder extends Decoder[NoDependencies] {
override def decode(s: String): NoDependencies[String] =
Kleisli { _ => Right(s.toLowerCase()) }
}
object SessionedDbAccess extends DatabaseAccess[DbSessionDependency] {
override def getData(): DbSessionDependency[String] = Kleisli { s =>
Right(s.runQuery)
}
}
Now when I want to use both objects with the business logic I have a conflict of type: Kleisli[ErrorOr, DbSession, A] is not compatible with Klesili[ErrorOr, Any, A].
val businessLogic: BusinessLogicService[DbSessionDependency] =
BusinessLogicService.make(PlainDecoder, SessionedDbAccess)
What would be the most "correct" way of composing the classes like this? I don't want to make my decoder to require the database session and I'm not really into creating a copy/wrapper around the Decoder as well.