【问题标题】:Kleisli dependencies with Tagless Final style具有无标签最终样式的 Kleisli 依赖项
【发布时间】:2021-04-10 03:03:09
【问题描述】:

我正在尝试使用 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
        }
}

现在我对 Decode 和 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)
    }
}

现在,当我想将这两个对象与业务逻辑一起使用时,我遇到了类型冲突: Kleisli[ErrorOr, DbSession, A] 与 Klesili[ErrorOr, Any, A] 不兼容。

val businessLogic: BusinessLogicService[DbSessionDependency] = 
    BusinessLogicService.make(PlainDecoder, SessionedDbAccess)

像这样组成类的最“正确”方式是什么?我不想让我的解码器需要数据库会话,而且我也不想在解码器周围创建一个副本/包装器。

【问题讨论】:

    标签: scala scala-cats kleisli tagless-final


    【解决方案1】:

    Kleisli(在 Cats 的 ReaderT monad 实现中)在输入类型上是逆变的:

    final case class Kleisli[F[_], -A, B](run: A => F[B]) { self =>
    ...
    

    这意味着Kleisli[ErrorOr, DbSession, A] 不是Kleisli[ErrorOr, Any, A] 的子类型,不能向上转换为它。

    反过来说,Kleisli[ErrorOr, Any, A]Kleisli[ErrorOr, DbSession, A] 的子类型。

    如果您认为Kleisli[F, In, Out] 在这里模拟In =&gt; F[Out],那么您会注意到DbSession =&gt; F[Out] 接受的输入少于Any =&gt; F[Out]。您可以将Any =&gt; F[Out] 用作DbSession =&gt; F[Out],但不能反过来使用,因为所有DbSession 输入也是有效的Any 输入,但并非所有Any 输入都是有效DbSession 输入(有人可以通过例如@987654338 @ 或 Int)。所以唯一安全的方法是让输入更具体(为不太具体的输入定义的函数总是可以处理更具体的输入)。

    这是通过In 参数中的逆变来建模的,这意味着超类型总是更具体。因此,在您的情况下,您不能期望推断的类型是Kleisli[ErrorOr, Any, A]。如果结合两个 Kleislis,一个取 Any,另一个取 DbSession,推断的输入应该是 Kleisli[ErrorOr, DbSession, A]

    【讨论】:

    • 感谢您的回答。您对参数和继承模型的定义是正确的。如以下帖子所述,这里的主要问题是:我缺少什么让编译器将 Decoder[NoDependencies] 视为 Decoder[DbSessionDependency] 的子类型。这很有效,因为 Kleisli 的第二个类型参数是逆变的。
    【解决方案2】:

    我从 Daniel Ciocîrlan(RockTheJVM 课程的作者,这很棒,顺便说一句)那里得到了最好的答案。

    Daniel Ciocîrlan:Kleisli 在输入类型(第二个类型参数)中是逆变的,因此 NoDependencies <: dbsessiondependency make plaindecoder decoder f>

    所以,它在什么时候起作用

    trait Decoder[+F[_]] {
        def decode(s: String): F[String]
    }
    

    【讨论】:

      猜你喜欢
      • 2019-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-08
      • 2013-09-11
      • 2014-08-27
      • 2020-07-29
      相关资源
      最近更新 更多