我正在尝试使用 Kleisli 对依赖项进行建模。例如,假设我有以下业务逻辑类型:
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
}
}
现在我有以下解码和数据库访问的实现:
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)
}
}
现在,当我想将这两个对象与业务逻辑一起使用时,我遇到了类型冲突:Kleisli[ErrorOr, DbSession, A] 与 Klesili[ErrorOr, Any, A] 不兼容。
val businessLogic: BusinessLogicService[DbSessionDependency] =
BusinessLogicService.make(PlainDecoder, SessionedDbAccess)
像这样组成类的最“正确”的方式是什么?我不想让我的解码器需要数据库会话,而且我也不想在解码器周围创建一个副本/包装器。