@functionWrapper please!

If you don't care about functions and you're fine with closures, you can already implement the mentioned LRU cache with property wrappers:

@propertyWrapper
class LRUCache<Parameter, Result> where Parameter: Hashable {
  let base: (Parameter) -> Result
  var cache: [Parameter: Result] = [:]
  var cacheInfo = (hits: 0, misses: 0)
  
  var wrappedValue: (Parameter) -> Result {
    return { parameter in
      if let cachedResult = self.cache[parameter] {
        self.cacheInfo.hits += 1
        return cachedResult
      } else {
        self.cacheInfo.misses += 1
        let result = self.base(parameter)
        self.cache[parameter] = result
        return result
      }
    }
  }
  
  var projectedValue: String {
    "Cache info: \(cacheInfo.hits) hits, \(cacheInfo.misses) misses, current size \(cache.count)"
  }
  
  init(wrappedValue function: @escaping (Parameter) -> Result) {
    self.base = function
  }
}

This example requires a function accepting a single parameter. With variadic generics it could be generalized. For simplicity the projected value returns the associated cache infos.

Python's lru_cache:

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

My attempt:

struct Main {
  @LRUCache static var fib: (Int) -> Int = { (n: Int) -> Int in
    if n < 2 { return n }
    return Main.fib(n - 1) + Main.fib(n - 2)
  }
}

>>> (0...15).map(Main.fib)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> Main.$fib
Cache info: 28 hits, 16 misses, current size 16

Try it!

3 Likes