我对效果的概念还不是很强,所以我的一些假设可能是完全错误的。每当您看到此类事件时,请纠正我。
我正在构建一个具有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 层,路由处理程序使用我尝试通过两种不同的变体(DummyService
和LiveService
实现的服务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
服务,并且应该类似于IO
。IO
定义了map
和flatMap
方法。在Live
服务级别上,refreshExchangeRatesCache
进行 REST 调用(使用http4s
,但这无关紧要),并且也应该在类似IO
上运行。
首先,我对上下文边界和Main
类F[_]
传播的假设是否正确?然后,是否可以在Live
服务级别上隐藏IO
类型?或者如何提供所需的隐式Monad
实例?
这是第一次遇到 F[] 类型。这: ConcurrentEffect:计时器上下文绑定表示有两个 在某处声明的实例:ConcurrentEffect[F[]] 和 Timer[F[_]] 如果我理解正确的话。
具体来说,它必须在隐式范围内声明。
对我来说唯一的一点谜团是为什么我们必须有这种应用性 含蓄。
您需要Applicative[F]
的证据,因为您的方法使用pure[F]
来提升ExchangeRate
F
上,其中pure
在Applicative
类型类中定义:
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)