Я еще не очень силен в концепции эффектов, поэтому некоторые из моих предположений могут быть совершенно неверными. Пожалуйста, исправьте меня всякий раз, когда вы видите такие случаи.
Я строю приложение (не с нуля, а скорее разрабатываю скелет) со скала-котами и котами-эффектами. Основной класс расширяет 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-уровень приложения, обработчик маршрута использует сервис, который я пытаюсь реализовать, с двумя разными вариантами — 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
. Кроме того, чтобы иметь возможность использовать оператор state.get.map(_.get(key))
в классе SelfRefreshingCache
, я должен использовать ограничение 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
и должно быть что-то вроде IO
. А для IO
определены методы map
и flatMap
. На уровне обслуживания Live
refreshExchangeRatesCache
выполняет вызов REST (используя http4s
, но это не имеет значения) и также должен работать на чем-то вроде IO
.
Прежде всего, верны ли мои предположения о границах контекста и распространении F[_]
от класса Main
? Могу ли я тогда скрыть тип IO
на уровне обслуживания Live
? Или как предоставить требуемый неявный экземпляр Monad
?