All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hael.jjm-core_2.13.0.2.3.source-code.Memo.scala Maven / Gradle / Ivy

The newest version!
package jjm

import cats.Id

import scala.concurrent.Future
import scala.concurrent.ExecutionContext

object Memo {

  // This should usually only do single-time execution of the given function.
  // but as of now it doesn't quite work that way in some cases. meh
  def memoizeFuture[A, B](
    f: A => Future[B],
    startingCache: Map[A, B] = Map.empty[A, B]
  ): A => OrWrapped[Future, B] = {
    // for thread-safety, make sure all cache updates are run on a single thread
    import java.util.concurrent.Executors
    implicit val ec = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor)
    var futCache = Map.empty[A, Future[B]]
    var cache = startingCache

    (a: A) => {
      cache.get(a).map(OrWrapped.pure[Future](_)).getOrElse {
        futCache.get(a).map(OrWrapped.wrapped(_)).getOrElse {
          val bFut = f(a) // failure case is if two threads get here concurrently, in which case both would run the function. meh
          // populate the future cache before attaching the callback to remove it,
          // and update the value cache before depopulating the future cache,
          // so nothing falls through the cracks.
          futCache = futCache + (a -> bFut)
          bFut.foreach { b =>
            cache = cache + (a -> b)
            futCache = futCache - a
          }
          OrWrapped.wrapped(bFut)
        }
      }
    }
  }

  type DelayedFuture[A] = () => Future[A]

  // see caveats in previous method regarding single-time execution
  // for thread-safety, you should pass in an execution context that makes
  // sure all cache updates are run on a single thread.
  def memoizeDotFuture[A <: Dot](
    f: DotKleisli[DelayedFuture, A],
    startingCache: DotMap[Id, A] = DotMap.empty[Id, A]
  )(implicit ec: ExecutionContext): DotKleisli[OrWrapped[DelayedFuture, *], A] = {
    var futCache = DotMap.empty[Future, A]
    var cache = startingCache
    new DotKleisli[OrWrapped[DelayedFuture, *], A] {
      override def apply(a: A) = {
        cache.get(a).map(OrWrapped.pure[DelayedFuture](_)).getOrElse {
          futCache.get(a).map[OrWrapped[DelayedFuture, a.Out]](
            fut => OrWrapped.wrapped[DelayedFuture, a.Out](() => fut)
          ).getOrElse[OrWrapped[DelayedFuture, a.Out]] {
            OrWrapped.wrapped[DelayedFuture, a.Out](
              () => {
                val bFut = f(a)()
                futCache = futCache.put(a)(bFut)
                bFut.foreach { b =>
                  cache = cache.put(a)(b)
                  futCache = futCache.remove(a)
                }
                bFut
              }
            )
          }
        }
      }
    }
  }

  // TODO
  // def memoizeDependentFuture[F[_], G[_]](

  // )

  // TODO in io/cats-effect extension
  // def memoizeWithRef[F[_]](
  //   f: A => F[B],
  //   cacheRef: Ref[F, Map[A, B]]
  // ): A => OrWrapped[F, B] = (a: A) =>  {
  //   cacheRef.get.flatMap { cache =>
  //     cache.get(a).map(pure[F](_)).getOrElse {
  //       f(a).flatMap { b =>
  //         cacheRef.update(_ + (a -> b)).as(b)
  //       }
  //     }
  //   }
  // }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy