有效服务找不到上下文绑定的隐式 monad 实例

Effectful service can not find context-bound implicit monad instance

我对效果的概念还不是很了解,所以我的一些假设可能是完全错误的。看到这种情况请指正。

我正在使用 scala-cats 和 cats-effects 构建一个应用程序(不是从头开始,而是开发骨架)。主 class 扩展 IOApp 并启动网络服务器:

object Main extends IOApp {

  override def run(args: List[String]): IO[ExitCode] =
    new Application[IO]
      .stream
      .compile
      .drain
      .as(ExitCode.Success)

}

class Application[F[_]: ConcurrentEffect: Timer] {

  def stream: Stream[F, Unit] =
    for {
      // ...
    } yield ()

}

第一次接触F[_]类型。 : ConcurrentEffect: Timer 上下文绑定表示在 某处 声明了两个实例:ConcurrentEffect[F[_]]Timer[F[_]] 如果我理解正确的话。

跳过应用程序的 HTTP 层,路由处理程序使用我试图通过两种不同变体实现的服务 - DummyServiceLiveService - Dummy 应该总是 return 常量(虚拟)数据,而 Live 发送 REST 请求并解析 JSON 对内部域模型的响应:

trait CurrencyConverterAlgebra[F[_]] {

  def get(currency: Currency): F[Error Either ExchangeRate]

}

class DummyCurrencyConverter[F[_]: Applicative] extends CurrencyConverterAlgebra[F] {

  override def get(currency: Currency): F[Error Either ExchangeRate] =
    ExchangeRate(BigDecimal(100)).asRight[Error].pure[F]

}

object DummyCurrencyConverter {

  // factory method
  def apply[F[_]: Applicative]: CurrencyConverterAlgebra[F] = new DummyCurrencyConverter[F]()

}

到目前为止一切顺利。对我来说唯一的谜团是为什么我们必须隐含 Applicative

但现在我尝试实现 Live 服务,该服务也将使用 Cache(限制请求):

trait Cache[F[_], K, V] {
  def get(key: K): F[Option[V]]

  def put(key: K, value: V): F[Unit]
}

private class SelfRefreshingCache[F[_]: Monad, K, V]
(state: Ref[F, Map[K, V]], refresher: Map[K, V] => F[Map[K, V]], timeout: FiniteDuration) extends Cache[F, K, V] {

  override def get(key: K): F[Option[V]] =
    state.get.map(_.get(key))

  override def put(key: K, value: V): F[Unit] =
    state.update(_.updated(key, value))

}

object SelfRefreshingCache {

  def create[F[_]: Monad: Sync, K, V]
  (refresher: Map[K, V] => F[Map[K, V]], timeout: FiniteDuration)
  (implicit timer: Timer[F]): F[Cache[F, K, V]] = {

    def refreshRoutine(state: Ref[F, Map[K, V]]): F[Unit] = {
      val process = state.get.flatMap(refresher).map(state.set)

      timer.sleep(timeout) >> process >> refreshRoutine(state)
    }

    Ref.of[F, Map[K, V]](Map.empty)
      .flatTap(refreshRoutine)
      .map(ref => new SelfRefreshingCache[F, K, V](ref, refresher, timeout))

  }

}

此处,SelfRefreshingCache 要求存在 Sync 实例 - 否则在尝试构建 Ref 实例时我会收到一条错误消息,指出它未定义。此外,为了能够在 SelfRefreshingCache class 中使用 state.get.map(_.get(key)) 语句,我必须使用 Monad 约束,大概是为了告诉 Scala 我的 F[_] 里面的类型 Cache 可以是 flatMap-ped.

在我的 Live 服务中,我尝试按如下方式使用此服务:

class LiveCurrencyConverter[F[_]: Monad](cache: F[Cache[F, Currency, ExchangeRate]]) extends Algebra[F] {

  override def get(currency: Currency): F[Error Either ExchangeRate] =
    cache.flatMap(_.get(currency))
      .map(_.toRight(CanNotRetrieveFromCache()))

}

object LiveCurrencyConverter {

  def apply[F[_]: Timer: ConcurrentEffect]: Algebra[F] = {
    val timeout = Duration(30, TimeUnit.MINUTES)

    val cache = SelfRefreshingCache.create[F, Currency, ExchangeRate](refreshExchangeRatesCache, timeout)
    // ---> could not find implicit value for evidence parameter of type cats.Monad[Nothing]

    new LiveCurrencyConverter(cache)
  }

  private def refreshExchangeRatesCache[F[_]: Monad: ConcurrentEffect](existingRates: Map[Currency, ExchangeRate]): F[Map[Currency, ExchangeRate]] = ???

}

目前,我遇到编译错误,提示我没有 Monad[Nothing] 的实例。这就是我的整个故事 Main 的转折点:如果我理解类型约束背后的整个概念(要求在方法调用的范围内定义隐式),那么应该传播 F[_] 类型从非常 Main 级别到我的 Live 服务,应该类似于 IOIO 同时定义了 mapflatMap 方法。在 Live 服务级别上,refreshExchangeRatesCache 进行 REST 调用(使用 http4s,但这应该无关紧要)并且应该 运行 类似 [=41] =] 还有。

首先,我关于上下文边界和 F[_]Main class 传播的假设是否正确?然后我可以在 Live 服务级别上隐藏 IO 类型吗?或者如何提供所需的 Monad 隐式实例?

将类型信息(在本例中为 F)添加到行

val cache = SelfRefreshingCache.create[F, Currency, ExchangeRate](refreshExchangeRatesCache[F], timeout)

This is the first encounter with the F[] type. The : ConcurrentEffect: Timer context-bound says that there are two instances declared somewhere: ConcurrentEffect[F[]] and Timer[F[_]] if I understand that correctly.

具体来说,必须在implicit scope.

中声明

The only bit of mystery to me is why we have to have that Applicative implicit.

您需要 Applicative[F] 的证据,因为您的方法使用 pure[F] 提升 ExchangeRateF,其中 pureApplicative 类型中定义 class:

ExchangeRate(BigDecimal(100)).asRight[Error].pure[F]

Also, in order to be able to use the state.get.map(_.get(key)) statement in the SelfRefreshingCache class, I have to use the Monad constraint

由于您使用的是 .map 而不是 .flatMap,因此 class 需要一个 Functor 而不是 Monad 的实例就足够了SelfRefreshingCache 的定义。对于伴随对象,您需要 Monad 才能 flatMap.

First of all, are my assumptions about context boundaries and F[_] propagation from the Main class correct?

是的,他们是。当您在 Main 和 "fill in" IO 中构建整个程序时,其中 F[_] 是必需的,编译器将在范围内搜索 IO 所需的所有隐式证据是否存在,假设您已经使用上下文边界或普通隐式参数从每个方法调用中捕获了要求。

Can I then hide the IO type on the Live service level?

IO 隐藏在您的方法中,因为 Live 只知道类型的 "shape",即 F[_]。需要立即解决您的问题,之前的答案已经说明您需要将 F 添加到您的方法调用中,以便编译器推断您打算填写哪种类型 refreshExchangeRatesCache.