Search code examples
scalafunctional-programminghigher-order-functionsfunction-composition

Functional way to get or update


I have a cache and have two functions to get an item from the cache and put an item to the cache.

When getting item(from the cache), if key not exists I need to populate a value to it and return that value.

Following is a sample code

class CacheComp {
    cache = Map[String, Foo]

    get(id): Foo = {
        // case(id exists) => Return matching Foo

        // case(id not exists) => Create a Foo and update the cache with created Foo. Then return updated Foo
    } 

    put(id, Foo) = {
        // put element to the cache   
    }
}

In here I'm violating single responsibility principle(SRP) of get(id) function. How to do this without violating SRP? I can simply rename the function to getOrUpdate(id). But are there any clean functional programming way to do that?


Solution

  • If you're aiming for a 'functional' solution, you want your cache Map to be immutable, because everything is immutable in the functional world. Note that scala.collection.immutable.Map has this method:

    override def updated [B1 >: B](key: A, value: B1): Map[A, B1]
    

    Now there is a small wrinkle -- once the map is updated, how do you use the cache with updated value? You need to change your interface for this.

    type Cache = Map[String, Foo]
    
    object Cache {
      def get(id: String, cache: Cache): (Foo, Cache) = cache.get(id) match {
        case Some(e) => (e,cache)
        case None => 
          val foo = makeFoo
          (foo, cache.updated(id, foo))
      }
    
      def put(id: String, foo: Foo, cache: Cache): Cache = cache.updated(id, foo)
    }
    

    That gives you a functional cache without side effects. I'd also further change put to upsert and check if a cache entry needs to be updated.