簡體   English   中英

具有無標記最終樣式的 Kleisli 依賴項

[英]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輸入(有人可以通過例如UnitInt )。 所以唯一安全的方法是使輸入更具體(為不太具體的輸入定義的函數總是可以處理更具體的輸入)。

這是通過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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM