Forked from vilu/gist:47560cfa287f1e7664800763f6d4dfe5
Created
February 7, 2019 12:03
-
-
Save igstan/104442ea863fb90321475edb1eac251a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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