[英]Kleisli dependencies with Tagless Final style
我正在嘗試使用 Kleisli 來 model 依賴項。 例如,假設我有以下業務邏輯類型:
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)
像這樣組成類的最“正確”的方式是什么? 我不想讓我的解碼器需要數據庫 session 並且我也不是真的要圍繞解碼器創建副本/包裝器。
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 => F[Out]
那么您會注意到DbSession => F[Out]
接受的輸入少於Any => F[Out]
。 您可以將Any => F[Out]
用作DbSession => F[Out]
但不能反過來使用,因為所有DbSession
輸入也是有效的Any
輸入,但並非所有Any
輸入都是有效的DbSession
輸入(有人可以通過例如Unit
或Int
)。 所以唯一安全的方法是使輸入更具體(為不太具體的輸入定義的函數總是可以處理更具體的輸入)。
這是通過In
參數中的逆變來建模的,這意味着超類型總是更具體。 因此,在您的情況下,您不能期望推斷的類型是Kleisli[ErrorOr, Any, A]
。 如果您結合兩個 Kleisli,一個采用Any
,另一個采用DbSession
,推斷的輸入應該是Kleisli[ErrorOr, DbSession, A]
。
我從 Daniel Ciocîrlan(RockTheJVM 課程的作者那里得到了最好的答案,順便說一句)。
Daniel Ciocîrlan:Kleisli 在輸入類型(第二類型參數)中是逆變的,所以 NoDependencies <: DbSessionDependency。 但是由於您在 make 工廠方法中傳遞了 PlainDecoder,因此您期望 PlainDecoder = Decoder[NoDependencies] <: Decoder[DbSessionDependency]。 這只有在解碼器在 F 中是協變的情況下才會發生。所以你需要解碼器[+F[_]]。
所以,它在什么時候起作用
trait Decoder[+F[_]] {
def decode(s: String): F[String]
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.