https://kotlinlang.org logo
#codereview
Title
# codereview
s

smallufo

09/17/2021, 5:29 PM
Hi , Is there any way to intercept method from interface and cache result “WITHOUT” spring’s method cache ? Take a simple calculator for example :
Copy code
interface ICalculate {

  fun multiply(a: Int, b: Int): Int
}
It just multiplies
a
and
b
, and return the result . Suppose it is a heavy computation work . And there are two implementations :
Copy code
class CalculatorDumb : ICalculate {
  override fun multiply(a: Int, b: Int): Int {
    var sum = 0
    (1..a).forEach {
      (1..b).forEach {
        sum++
      }
    }
    return sum
  }
}
The dumb implementation just add one by one .
Copy code
class CalculatorSmart : ICalculate {

  override fun multiply(a: Int, b: Int): Int {
    return a * b
  }
}
And this smart implementation just returns
a * b
. OK , here is the point . I hope client can initialize no matter dumb or smart implementation , and can get result if the parameter is identical. There is a
Memoize
pattern , described here : https://jorgecastillo.dev/kotlin-purity-and-function-memoization :
Copy code
class Memoize<in T, out R>(val f: (T) -> R) : (T) -> R {
  private val values = mutableMapOf<T, R>()
  override fun invoke(x: T): R {
    return values.getOrPut(x) { f(x) }
  }
}

fun <T, R> ((T) -> R).memoize(): (T) -> R = Memoize(this)
I can use it in the implementation class , like this :
Copy code
class CalculatorSmart : ICalculate {

  data class CacheKey(val a: Int, val b: Int)

  private val innerCalculate: (CacheKey) -> Int = { key: CacheKey ->
    println("cache miss")
    key.a * key.b
  }.memoize()

  override fun multiply(a: Int, b: Int): Int {
    return innerCalculate(CacheKey(a, b))
  }
}
But it seems it’s hard to apply it in the interface layer. I wonder if there are any patterns to achieve : 1. Each implementation class ( dumb or smart in this example) doesn’t need to implement its cache . 2. There are no two versions of method (
multiply()
and
cachedMultiply()
for example ) 3. Client only knows one method of the interface , No matter the client initialize smart or dumb class , the result of the same parameter will be cached and returned. For example : such scenario is OK
Copy code
val calSmart: ICalculate = CalculatorSmart()
println(calSmart.multiply(3, 7)) // cache miss
println(calSmart.multiply(3, 7)) // cache hit

val calDumb: ICalculate = CalculatorDumb()
println(calDumb.multiply(3, 7)) // cache miss
println(calDumb.multiply(3, 7)) // cache hit
It will work like Spring’s method cache . but I hope there will be a kotlin-idiomatic style , maybe more functional , just like the memoization pattern above . Is there any idea ? Thanks.
j

Jukka Siivonen

09/18/2021, 8:33 AM
1. Maybe some kind of factory which wraps actual implementations around cache implementation. Not sure though if constructors for different implementations can be private so only factory may create instances
e

ephemient

09/18/2021, 9:26 PM
I'd do something along the lines of https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-deep-recursive-function/ with a scope that allows for recursion, and definitely the memoize cache needs to have some bounds
Copy code
fib(100) = Failure(java.util.concurrent.TimeoutException)
memoFib(100) = TimedValue(value=354224848179261915075, duration=4.192067ms)
ack(3, 12) = Failure(java.util.concurrent.TimeoutException)
memoAck(3, 12) = TimedValue(value=32765, duration=150.723097ms)
1
3 Views