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



我对效果的概念还不是很强,所以我的一些假设可能是完全错误的。每当您看到此类事件时,请纠正我。

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

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应该始终返回常量(虚拟)数据,而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类中使用state.get.map(_.get(key))语句,我必须使用Monad约束,大概是告诉 Scala 我在Cache中的F[_]类型可以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,但这无关紧要),并且也应该在类似IO上运行。

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

这是第一次遇到 F[] 类型。这: ConcurrentEffect:计时器上下文绑定表示有两个 在某处声明的实例:ConcurrentEffect[F[]] 和 Timer[F[_]] 如果我理解正确的话。

具体来说,它必须在隐式范围内声明。

对我来说唯一的一点谜团是为什么我们必须有这种应用性 含蓄。

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

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

另外,为了能够使用state.get.map(_.get(key))语句在 SelfRefreshingCache 类中,我必须使用 Monad 约束

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

首先,是我对上下文边界和 F[_] 的假设 从主类传播正确吗?

是的,他们是。当您在Main中构建整个程序并在需要F[_]的地方"填充"IO时,编译器将在范围内搜索 IO 所需的所有隐式证据的存在,前提是您已使用上下文边界或纯隐式参数从每个方法调用中捕获了需求。

然后,是否可以在实时服务级别隐藏 IO 类型?

IO隐藏在您的方法中,因为Live只知道类型的"形状",即F[_].需要立即解决您的问题,前面的答案指出您需要在方法调用中添加F,以便编译器推断您要refreshExchangeRatesCache填写哪种类型。

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

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

最新更新