Memoization is an optimization technique when function evaluation results get cached by the set of passed parameters. This allows to evaluate the function only at the first time it is called with a specific set of arguments and return the cached results on all the consecutive calls with the same set of arguments.
You can think of it as of a more general lazy that also distinguishes the parameters. Thus memoization on functions with no arguments, such as memoized def functionWithNoParameters: String will have absolutely the same effect as lazy val functionWithNoParameters: String.
memoized def calculateSomething(a: Int, b: Int): String = {
// some heavy calculations take place here
}
calculateSomething(1, 2) // since it's the first time the function is called with
// parameters `(1, 2)` it will actually evaluate, then
// it will store the result in cache and return this result
calculateSomething(1, 2) // since the function has already been called with this
// set of parameters it will quickly just fetch a result
// from the underlying cache, and no actual evaluation will
// take place
calculateSomething(1, 3) // will calculate again, since yet there's no record for
// a `(1, 3)` arguments set in cache yet
// the following will always return the same instance of `ListWrapper` per each list
memoized implicit def listWrapper[T](list: List[T]) = new ListWrapper(list)
class ListWrapper(source: list) {
lazy val cachedPermutations = list.permutations
}
List(1, 3, 4, 4, 4, 3, 3).cachedPermutations // will calculate
List(1, 3, 4, 4, 4, 3, 3).cachedPermutations // will fetch the result from cache
memoized def resolveSomething(a: Int, b: Int): String =
a.toString + b.toString
This would generate a code similar to the following:
private val resolveSomethingCache = collection.mutable.Map[(Int, Int), String]()
def resolveSomething(a: Int, b: Int) =
try resolveSomethingCache((a, b))
catch {
case _ => {
val result = {
// actual implementation relies here:
a.toString + b.toString
}
resolveSomethingCache.update((a, b), result)
result
}
}
Please note that in the implementation above I didn't take multithreading into account.
I'm not sure about the correct procedure to make it go into the actual proposal. On the SIP documentation I've seen a recommendation to discuss it here. Just in case, I've also posted an appropriate issue on the tracker: https://issues.scala-lang.org/browse/SI-5741
2. Implicits keeping context or state
// the following will always return the same instance of `ListWrapper` per each list memoized implicit def listWrapper[T](list: List[T]) = new ListWrapper(list) class ListWrapper(source: list) { lazy val cachedPermutations = list.permutations } List(1, 3, 4, 4, 4, 3, 3).cachedPermutations // will calculate List(1, 3, 4, 4, 4, 3, 3).cachedPermutations // will fetch the result from cache
val l = List(1, 3, 4, 4, 4, 3, 3)
l.cachedPermutations // will calculate
l.cachedPermutations // will fetch the result from cachememoized def resolveSomething(a: Int, b: Int): String = a.toString + b.toString
On memory and setup issuesAs far as I see the only need for customization of memoization behaviour is custom memory management. But I see no need for that.The actual cache of the function gets held on the context object on which the function gets called as a method. So as soon as this object loses all pointers to, it should get garbage collected with all the cache of the memoization. The issue will preserve for the static singleton objects though, but anyway I believe that memoization must be used with care.Please correct me if I'm wrong.I strongly vote for keeping it as simple as possible.
A "lazy function" is a non-strict function which caches its value onceevaluated.