views:

310

answers:

7

By "immutable function" or "immutable method", I mean a function whose result will never vary if you give it the same arguments.

I would be interested to know if anyone know of a more generic or less verbose solution when you want to cache the precomputed value(s) of an immutable function.

Let me explain what I mean with a simple example:

//Let's assume that ComputeStuff() returns a widely used value 
//and that 
//1. It is immutable (it will always return the same result)
//2. its performance is critical, and it cannot be accepted to compute
//   the result at each call, because the computation is too slow
//I show here a way to solve the problem, based on a cached result.
//(this example works in a case of a method with no arguments. 
// A hash would be required in order to store multiple precomputed results 
//depending upon the arguments)
private string mComputeStuff_Cached = null;
public string ComputeStuff()
{
  if (mComputeStuff_Cached != null)
    return mComputeStuff_Cached ;

  string result;
  //
  // ...
  //Do lots of cpu intensive computation in order to compute "result" 
  //or whatever you want to compute
  //(for example the hash of a long file)
  //...
  //

  mComputeStuff_Cached  = result;
  return mComputeStuff_Cached ;
}

Notes:
- I added the tag C++ as a solution in C++ would also interest me
- The concept of "immutable functions" is common for database developers, since a function can be defined as "immutable", or "immutable within a transaction" (this is a good way to improve the performance of the queries).

Thanks in advance

A: 

A C++ solution would be very similar to what you have outlined, differing only in the heady mix of const qualified public interface(s) and (some) mutable members:

class Computer {
    mutable string cache;
  public:
    // I wouldn't call it ComputeXXX
    // since I want to hide the implementation
    // details from my client: for the client
    // there is no change in state due to a call
    // to this function
    const string& StringVal() const {
         if (cache.empty()) {
                // compute cache
         }
         return cache;
    }
    // ...
};
dirkgently
I'm not sure if a check to see if mComputeStuff_Cached is empty() is sufficient. It could be that empty() is a legitimately cached result. Depends on the application I guess.
Doug T.
Yes. But that's the closest we can in C++ to C#'s null, I guess.
dirkgently
Or for a higher difficulty tariff, use pthread_once (or boost::call_once) to thread-safely populate the cache. Which also solves the "empty value" problem.
Steve Jessop
Why are you returning a copy of the string rather than a const reference? Once computed it will not be changing.
Martin York
@Martin York: Updated. Silly slip.
dirkgently
@onebyone.livejournal.com: The OP said this was a trivial program. I specifically wanted to avoid any references to multi-threading and issues thereof.
dirkgently
A: 

You can make it slightly less verbose:

private string mComputeStuff_Cached = null;
public string ComputeStuff()
{
  if (mComputeStuff_Cached == null) {
    string result;
    //
    // ...
    //Do lots of cpu intensive computation in order to compute "result" 
    //or whatever you want to compute
    //(for example the hash of a long file)
    //...
    //

    mComputeStuff_Cached  = result;
  }

  return mComputeStuff_Cached ;
}

Other notes on this type of pattern:

  • If you use C++, and such a method const, then modifying members will not be possible because they are also treated as const within the context of that method. However, you can override this by marking the member(s) you need to modify as mutable.
  • There is a difference between "semantic const" and "bitwise const". When an author marks something as const, they usually mean "semantic const" (which is what you mean in this case), but the compiler can only enforce "bitwise const".
  • const methods usually give the caller the impression that they can be called from multiple threads simultaneously, because they have no side effects. In this type of pattern you have a "bitwise" side effect, but no "semantic" side effect. Therefore if it is possible that multiple threads could call such a method you must internally protect this initialization.
Jared Oberhaus
A: 

You can make your member variable mutable (keyword).

This will allow a const member function modify this value. I use it all the time to cache intermediate results.

Tim Rupe
A: 

You can rewrite this code almost verbatim in C++

class CClass
{

  private:
     std::string* mComputeStuff_Cached;

  public:
     CClass()
       mComputeStuff_Cached(NULL)
     {

     }

     ~CClass()
     {
           delete mComputeStuff_Cached;
     }


     std::string ComputeStuff()
     {
         if (mComputeStuff_Cached != NULL)
         {
             return mComputeStuff_Cached
         }
         else
         {
             std::string calcedAnswer;
             ...
             // store away answer
             mComputeStuff_Cached = new std::string(calcedAnswer);
         }
     }
};

I'm not sure if a check to see if mComputeStuff_Cached is empty() is sufficient. It could be that empty() is a legitimately cached result.

Doug T.
+2  A: 

"Memoization" may be a useful term, here. There are a few memoization libraries out there (I could swear there was one in boost, but I can't find it at the moment). Doing a web search for "memoize" or "memoization" and your language of choice will reveal a few hits.

Here's a neat article in Wikibooks: Optimizing C++/General optimization techniques/Memoization

leander
+1  A: 

Well, using a delegate such as Func<T> can make it more re-usable without requiring polymorphism / inheritance - but there is nothing more "inbuilt" in C#:

using System;
static class Program {
    static void Main() {
        var func = CachedFunc.Create(() => int.Parse(Console.ReadLine()));

        Console.WriteLine(func.Value);
        Console.WriteLine(func.Value);
    }
}
static class CachedFunc {
    public static CachedFunc<T> Create<T>(Func<T> func) {
        return new CachedFunc<T>(func);
    }
}
class CachedFunc<T> {
    T value;
    Func<T> func;
    public CachedFunc(Func<T> func){
        if (func == null) throw new ArgumentNullException("func");
        this.func = func;
    }
    public T Value {
        get {
            if (func != null) {
                value = func();
                func = null;
            }
            return value;
        }
    }
    public static explicit operator T(CachedFunc<T> func) {
        return func.Value; }
}
Marc Gravell
+1  A: 

You could use the static keyword inside the function. It will be computed only once:

std::string GetWidelyUsedValue()
{
   static std::string value = ComputeStuff() ;
   return value ;
}

std::string ComputeStuff()
{
   // Compute the string here.
}
swongu
Thanks for the answer. However, note that this works only if you do not require your code to be thread-safe : cf http://blogs.msdn.com/oldnewthing/archive/2004/03/08/85901.aspx
Pascal T.
Yes, that's definitely true. I should have put a disclaimer.
swongu
So you just and add a simple mutex and this would work, in any case this **is** the way such things are done in C++ in 99% of cases.
Artyom