Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save igstan/104442ea863fb90321475edb1eac251a to your computer and use it in GitHub Desktop.
Save igstan/104442ea863fb90321475edb1eac251a to your computer and use it in GitHub Desktop.
package com.example.http4splayground.services
import java.util.Date
import java.util.concurrent.TimeUnit
import cats._
import cats.data.StateT
import cats.effect.{ExitCode, IO, IOApp}
import cats.implicits._
import cats.mtl.MonadState
import cats.mtl.implicits._
import com.example.http4splayground.data.{Rating, Restaurant, RestaurantId}
import scala.annotation.tailrec
import scala.concurrent.duration.FiniteDuration
import scala.util.Random
// Algebra
trait RestaurantFetcher[F[_]] {
def get: F[List[RestaurantId]]
}
object FetcherModule {
def liftM[F[_],S,A](fetcherModule: FetcherModule[F]):StateT[F,S,A] = ???
}
// Algebra
trait RatingFetcher[F[_]] {
def getRatings(rId: RestaurantId): F[Option[Restaurant]]
}
trait Fetcher[F[_]] {
def initial: F[List[Restaurant]]
def update(old: List[Restaurant]): F[List[Restaurant]]
}
// Module
final case class FetcherModule[F[_] : Monad](restaurantFetcher: RestaurantFetcher[F], ratingFetcher: RatingFetcher[F]) extends Fetcher[F] {
override def initial: F[List[Restaurant]] = Monad[F].pure(List.empty[Restaurant])
override def update(oldRs: List[Restaurant]): F[List[Restaurant]] = (for {
rIds <- restaurantFetcher.get
newRs <- Traverse[List].sequence(rIds.map(rId => ratingFetcher.getRatings(rId))).map(_.flatten)
updatedRs = (rsToRMap(oldRs) ++ rsToRMap(newRs)).values.toList
} yield Monad[F].pure(updatedRs)).flatten
private def rsToRMap(oldRs: List[Restaurant]): Map[RestaurantId, Restaurant] = {
oldRs.foldRight(Map.empty[RestaurantId, Restaurant]) { case (r@Restaurant(rId, _), acc) => acc + (rId -> r) }
}
}
object RunWithStateT extends IOApp {
trait LocalClock[F[_]] {
def now: F[Long]
}
trait Sleep[F[_]] {
def sleep(time: FiniteDuration): F[Unit]
}
def run(args: List[String]): IO[ExitCode] = {
val random = new Random()
val restaurantFetcher = new RestaurantFetcher[StateT[IO,List[Restaurant],?]] {
override def get: StateT[IO, List[Restaurant], List[RestaurantId]] = {
StateT.liftF(IO.delay {
(1 to random.nextInt(10)).map(i => RestaurantId(s"${new Date()}-restaurant")).toList
})
}
}
val ratingFetcher = new RatingFetcher[StateT[IO,List[Restaurant],?]] {
override def getRatings(rId: RestaurantId): StateT[IO, List[Restaurant], Option[Restaurant]] =
StateT.liftF(IO.delay {
Option(Restaurant(rId, Rating(random.nextInt(6).toDouble, 1)))
})
}
val sleep = new Sleep[StateT[IO,List[Restaurant],?]] {
override def sleep(time: FiniteDuration): StateT[IO, List[Restaurant], Unit] =
StateT.liftF(IO.sleep(time))
}
val fetcher: FetcherModule[StateT[IO, List[Restaurant], ?]] = FetcherModule[StateT[IO, List[Restaurant], ?]](restaurantFetcher, ratingFetcher)
(for {
start <- fetcher.initial
_ <- loop[StateT[IO, List[Restaurant], ?]](fetcher, sleep).foreverM[Unit]
} yield ()).run(List.empty[Restaurant]).as(ExitCode.Success)
}
private def loop[F[_] : Monad](A: FetcherModule[F], S: Sleep[F])(implicit F: MonadState[F, List[Restaurant]]): F[Unit] = {
for {
old <- F.get
_ = println(s"old: $old")
updated <- A.update(old)
_ = println(s"updated: $updated")
_ <- F.set(updated)
_ <- S.sleep(FiniteDuration(100, TimeUnit.MILLISECONDS))
} yield ()
}
}
object RunWithIO extends IOApp {
trait LocalClock[F[_]] {
def now: F[Long]
}
trait Sleep[F[_]] {
def sleep(time: FiniteDuration): F[Unit]
}
def run(args: List[String]): IO[ExitCode] = {
val random = new Random()
val restaurantFetcher = new RestaurantFetcher[IO] {
override def get: IO[List[RestaurantId]] = {
IO.delay {
(1 to random.nextInt(10)).map(i => RestaurantId(s"${new Date()}-restaurant")).toList
}
}
}
val ratingFetcher = new RatingFetcher[IO] {
override def getRatings(rId: RestaurantId): IO[Option[Restaurant]] =
IO.delay {
Option(Restaurant(rId, Rating(random.nextInt(6).toDouble, 1)))
}
}
val sleep = new Sleep[IO] {
override def sleep(time: FiniteDuration): IO[Unit] =
IO.sleep(time)
}
val fetcher: FetcherModule[IO] = FetcherModule[IO](restaurantFetcher, ratingFetcher)
(for {
start <- fetcher.initial
_ <- loop[IO](fetcher, sleep)(start)
} yield ()).as(ExitCode.Success)
}
/**
* Would not work because it's not tail-recursive
*/
// @tailrec
private def loop[F[_] : Monad](A: FetcherModule[F], S: Sleep[F])(old:List[Restaurant]): F[Unit] = {
for {
updated <- A.update(old)
_ = println(s"old: $old")
_ = println(s"updated: $updated")
_ <- S.sleep(FiniteDuration(1, TimeUnit.SECONDS))
_ <- loop(A,S)(updated)
} yield ()
}
/**
* Doesn't work because it never goes out of the recursion
*/
@tailrec
private def loop2[F[_] : Monad](A: FetcherModule[F], S: Sleep[F])(oldF:F[List[Restaurant]]): F[Unit] = {
val result = for {
old <- oldF
updated <- A.update(old)
_ = println(s"old: $old")
_ = println(s"updated: $updated")
_ <- S.sleep(FiniteDuration(1, TimeUnit.SECONDS))
} yield updated
loop2(A,S)(result)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment