如何使用 Kleisli 访问调用链的每个函数中的上下文?

How can I access the context in every function of a call chain with Kleisli?

我有一些方法的调用链,我通过 Kleisli 传递上下文。基本上我想将一个上下文向下传递到数据库访问层,但我想在两者之间的任何地方访问这个上下文。

下面的例子很完美。但我的问题是,我也想访问 OrderService.findAll(...) 中的上下文。我尝试了几种方法,但我总是失败。

object OrderRepository {
    fun findAll(userId: String): Kleisli<Context, ForIO, List<Order>> =
        Kleisli { ctx ->
            IO {
                ctx.db.query("someQuery")
            }
        }
}

object OrderService {
    fun findAll(userId: String): Kleisli<Context, ForIO, List<OrderResponse>> =
         OrderRepository.findAll(userId).map(IO.monad()) { orderList ->
            orderList.map {
                //Create OrderResponse from Order
            }
        }
}

是否可以访问那里的上下文,或者这没有任何意义?感谢您的帮助:)

你需要的是从 D 到 D 的 Kleisli,以 D 为上下文。这样你也可以将 D (上下文)作为结果类型,并且你可以 flatMap 并访问它。这就是方法 ask() 提供的原因,可通过同伴获得。

假设您的 OrderRepository 也是上下文中的依赖项而不是纯函数(为了示例),因此您需要从服务的上下文中访问它。参见:

interface OrderApi
interface OrderDB {
  fun query(query: String): List<Order> = TODO()
}

data class Order(val id: String)
data class OrderResponse(val order: Order)
data class Context(val api: OrderApi, val repository: OrderRepository, val db: OrderDB)

class OrderRepository {
  fun findAll(userId: String): Kleisli<Context, ForIO, List<Order>> =
    Kleisli { ctx ->
      IO {
        ctx.db.query("someQuery")
      }
    }
}

object OrderService {
  fun findAll(userId: String): Kleisli<Context, ForIO, List<OrderResponse>> {
    val monad = IO.monad()
    return Kleisli.ask<Context, ForIO>(monad).flatMap(monad) { ctx ->
      ctx.repository.findAll(userId).map(monad) { orderList ->
        orderList.map { OrderResponse(it) }
      }
    }
  }
}

也就是说,Kleisli 是一个 Monad 转换器(也称为 ReaderT),使用起来可能有点复杂。如果您想在功能代码库上注入依赖项并保持简单,我的建议是通过 Context 接收器使用扩展功能,它已经隐式地将您的依赖项跨所有级别传递,这在 on this post by Paco.[=13= 中进行了描述]

@brewcode @bob 在 FP 中有一个高于 MTL 等编码类型的高阶模式,即定界延续,所有 monad 之母。这将命令式语法包含在整个 Functor 层次结构中,包括 monad,例如 readers 通过延续将应用程序语法带到环境中。这就是 kotlin 悬架。

@Jorge Castillo 的问题是正确的,但任何支持延续的语言都比以分配成本包装数据类型具有更好的抽象。

对于 JS 和 TS,而不是像 ReaderT 那样的 Haskell 编码或像 Scala 和 Haskell 这样的任何回调样式编码,如果这些语言中有一种方法,建议基于定界延续与 ReaderT 提议的包装回调样式相比,对于初学者来说会更简洁、更容易使用。查看下面的程序,看看它是否比包装版本有任何缺点。

interface OrderApi

interface OrderDB {
  fun query(query: String): List<Order> = TODO()
}

data class Order(val id: String)

data class OrderResponse(val order: Order)

data class Context(
  val api: OrderApi,
  val repository: OrderRepository,
  val db: OrderDB) : OrderApi by api, OrderRepository by repository, OrderDB by db

interface OrderRepository {
  suspend fun Context.findAll(userId: String): List<Order> =
    query("someQuery")
}

object OrderService {
  suspend fun Context.findAll(userId: String): List<OrderResponse> =
    findAll(userId).map(::OrderResponse)
}